Friday 8 August 2014

Parallel Programming 01

In my previous post we had an intro to the TPL and the async/await keywords and that they're the same thing uisng console apps, in this post it's going to get more interesting we're going to work with these in a winRT application, what I made was a UI with two progress bars, two Textbox's and a button, kinda looks like so


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";

}

and for our LongRunnting Task we simply made

public void LongRunningTask(ProgressBar pb)
{
    for (var i = 0; i <= 100; i+=20)
    {
        Task.Delay(1000).Wait();
        pb.Value = i;
    }
}

now what you'd call rocket science but lets push on, now what would you expect to happen here? I'll tell you what I'd expect
  • 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.
unfortunately the only thing I got right was that the UI would freeze for the 10s, all the code fired in the background and once the event was finished the UI was updated.

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;               
    }

}

now with that minor change we get much different behavior as soon as we hit start:

  • both textboxes are populated immediately 
  • both progress bars start immediately 
  • the dialog appeared immediately
  • the UI was responsive 
not what we we're going for but a whole lot better than what we had, basically we fired everything asynchronously and didn't await for anything to complete, so it all fired at the same time.

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";
}

this time

  • 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";   
        });

}

so instead of using the async/await we used the task.run().ContinueWith, now first thing yes this is valid you can keep chaining ContinueWiths, i'm sure there's a limit, but i don't know what it is. Howerver we're going to have a different problem, if we run this we'll get an error.

"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"));

}

now event here, we don't wait for the dialog, needless to say the complexity is far greater than with the async await keywords.