Not going to win any design awards but gives you an idea of what i'll be referring to. To get started we created a simple event handler for the click event of the button.
void Start_Button_Click(object sender, RoutedEventArgs e)
{
//start
the first long running task
LongRunningTask(Progress00_ProgresBar);
//print
something to the textbox
TextBoxOne.Text = "First Progress Bar Complete";
//show a
message to the user
new MessageDialog("Good Times").ShowAsync();
//start
the second long running task
LongRunningTask(Progress01_ProgresBar);
//print
something to the second textbox
TextBoxTwo.Text = "Second Progress Bar Complete";
}
public void LongRunningTask(ProgressBar pb)
{
for (var i = 0; i <= 100; i+=20)
{
Task.Delay(1000).Wait();
pb.Value = i;
}
}
- i would expect:
- the first progress bar to increment 5 times in 5s
- the first textbox to fill in once it's complete
- a dialog to show up with the words good times
- immediately with the dialog still visible for the second progress bar to start same as the first
- then finally for the second text box to be populated
- all the while i would expect the UI to stay frozen.
Let's try and fix this, add the async keyword to our long running function, make it return a Task and change the Task.Delay(1000).Wait() to await Task.Delay(1000);
public async Task LongRunningTask(ProgressBar pb)
{
for (var i = 0; i <= 100; i += 20)
{
await Task.Delay(1000);
pb.Value = i;
}
}
- both textboxes are populated immediately
- both progress bars start immediately
- the dialog appeared immediately
- the UI was responsive
now let's modify our click event handler, make it async and await the progress bars.
async void Start_Button_Click(object sender, RoutedEventArgs e)
{
//start
the first long running task in parallel
await LongRunningTask(Progress00_ProgresBar);
//print
something to the textbox
TextBoxOne.Text = "First Progress Bar Complete";
//show a
message to the user
new MessageDialog("Good Times").ShowAsync();
//start
the second long running task in parallel
await LongRunningTask(Progress01_ProgresBar);
//print
something to the second textbox
TextBoxTwo.Text = "Second Progress Bar Complete";
}
- the first porgress bar starts, when it completes
- the first textbox is populated
- the dialog appears
- immediately after the first textbox is populated the second progress bar starts
- once it's done the second textbox is populated.
- the whole time the UI is responsive
much closer to what we where looking for, let's make one more modification add the await keyword to the message dialog.
await new MessageDialog("Good Times").ShowAsync();
Now instead of the second progress bar starting immediately it waits for the user to dismiss the Dialog.
So that was easy... now let's try it without the async/await keywords and just use tasks.
void Start_Button_Click(object sender, RoutedEventArgs e)
{
//start
the first long running task in parallel
Task.Run(() => LongRunningTask(Progress00_ProgresBar))
.ContinueWith(tr =>
{
//print
something to the textbox
TextBoxOne.Text = "First Progress Bar Complete";
}).ContinueWith(async tr =>
{
//show a
message to the user
await new MessageDialog("Good Times").ShowAsync();
}).ContinueWith(tr =>
{
//start
the second long running task in parallel
Task.Run(() => LongRunningTask(Progress01_ProgresBar));
}).ContinueWith(tr => {
//print
something to the second textbox
TextBoxTwo.Text = "Second Progress Bar Complete";
});
}
"The application called an interface that was marshalled for a different thread."
this brings us to another caveat, in winRT applications there's a UI thread, It's the only allowed to update the user interface, so when we create tasks, we can't update the UI from within them directly. there is a construct that will allow us to reach out and update the thread from a task
await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => /* function to run on the UI */);
so lets modify our long running task to use it.
async Task<int> LongRunningTask(ProgressBar pb)
{
for (var i = 0; i <= 100; i += 20)
{
await Task.Delay(1000);
await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => pb.Value = i);
}
return 0;
}
we also have to update our event handler because of the textboxes and dialog.
void Start_Button_Click(object sender, RoutedEventArgs e)
{
//start
the first long running task in parallel
Task.Run(() => LongRunningTask(Progress00_ProgresBar))
.ContinueWith(async tr =>
await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() => TextBoxOne.Text = "First Progress Bar Complete"))
.ContinueWith(tr =>
this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
async () => await new MessageDialog("Good Times").ShowAsync()))
.ContinueWith(tr =>
LongRunningTask(Progress01_ProgresBar).Wait())
.ContinueWith(async tr =>
await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() => TextBoxTwo.Text = "Second Progress Bar Complete"));
}