Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

.NET asynchrony in the UI context

0.00/5 (No votes)
21 Nov 2011 1  
UI and concurrent programming using IAsyncResult, BackgroundWorker, TPL, and ‘async’ syntax.

Release

Table of Contents

Introduction

Most of the code we still write today is synchronous, but sometimes having multithreaded applications is unavoidable. Today's platforms (software frameworks and mostly hardware), have improved in order to accommodate this growing need. But relying only on primitives in the System.Threading Namespace [^] alone does not make programming too easy, especially when it involves UI elements that have thread intrinsic affinity. Hence, Microsoft has tried to advance .NET asynchronous programming by establishing some patterns [^] and adding compiler support. The first pattern was based on the delegate IAsyncResult [^] in .NET 1.1, and then it appeared to settle on event-based asynchrony [^] in .NET 2.0 using BackgroundWorkers [^], until Task Parallel Library [^] (TPL) emerged in .NET 4.0 and finally the upcoming async language syntax [^] on top of it. The latter will most likely show up in the next version of Visual Studio, presumably in 2012.

Background

This article is more like an exercise of how to start with some crude synchronous code and parallelize it using the patterns I have mentioned. It is not intended to be an introduction for any of the topics I have just enumerated above. As of the time I am writing this article, Visual Studio 2012 is still in beta stage and in order to use Visual Studio 2010, you will need to install Visual Studio Async CTP (SP1 Refresh) [^] to run the code. I am not intending to pick winners and losers, but just to present some ways of dealing with concurrency and UI using .NET/C# (VB should be very similar). Be prepared to spend some time installing this package. Throughout this exercise, I will try to achieve three goals for the code:

  1. The UI should stay responsive and get updated when any action completes
  2. The code should execute its actions in parallel and independently
  3. The user should be notified when all the concurrent actions have completed

During this article, I will not be talking about a plain multithreading [^] implementation, cancellation, or timeout in order to keep things simple. We will observe the outcome and you will see what it takes to achieve the goals mentioned above in different ways.

How it works

Unlike most of the online information that focuses on some sort of lengthy IO operation like download, I'm going to create my own 'lengthy' function that takes a string as an input argument and returns another string. I will take the opportunity to spy a little bit on the inside of the different parallel implementations by returning the duration of the action and most importantly the ManagedThreadId [^] when the 'lengthy' action is executing. Rather than having only a single parallel activity running along the UI thread, I will spawn several concurrent activities. The function code and the arguments that will eventually be passed in for each action and the procedure are shown below:

string[] _myArguments = { "7", "4", "bad", "3" };

private static string MyFunction(string myArg)
{
    Stopwatch duration = Stopwatch.StartNew();
    try
    {
        int pause = int.Parse(myArg);
        Thread.Sleep(pause * 1000);//make seconds
        duration.Stop();
        return string.Format(
              "MyFunction with argument '{0}' lasted {1} ms on thread {2}",
              myArg, duration.ElapsedMilliseconds, 
              Thread.CurrentThread.ManagedThreadId);
    }
    catch (Exception ex)
    {
        duration.Stop();
        throw new ApplicationException(string.Format(
                  "Exception '{3}' thrown for argument '{0}'." + 
                  "It lasted {1} ms on thread {2}",
                  myArg, duration.ElapsedMilliseconds, 
                  Thread.CurrentThread.ManagedThreadId, 
                  ex.Message), ex);
    }
}

The duration of the operation (in seconds) is determined by the input argument and if the argument is not a numeric string (e.g., "bad"), it will throw an exception, to cover this case too.

Starting with the original synchronous code

Let's start with some simple synchronous code in a loop that we will try to make it concurrent later using different techniques. The code below updates a text box with the results as they become available.

private void _buttonSynch_Click(object sender, EventArgs e)
{
    DisableUIControls(true);
    _textBox.Text = string.Format(
       "Using synchronous calls: Main thread Id is {0}\r\n",
       Thread.CurrentThread.ManagedThreadId);
    Stopwatch duration = Stopwatch.StartNew();
    foreach (string arg in _myArguments)
    {
        Application.DoEvents();
        try
        {
            _textBox.Text += string.Format(
                    "Result on thread {0}:\"{1}\"\r\n",
                    Thread.CurrentThread.ManagedThreadId, 
                    MyFunction(arg));
        }
        catch (Exception ex)
        {
            _textBox.Text += string.Format(
            "Exception thrown on thread {0}:\"{1}\"\r\n",                              
            Thread.CurrentThread.ManagedThreadId, ex.Message);
        }
    }
    duration.Stop();
    _textBox.Text += string.Format(
                     "The UI thread {0} was blocked for {1} ms\r\n",
                     Thread.CurrentThread.ManagedThreadId, 
                     duration.ElapsedMilliseconds);
    DisableUIControls(false);
}

And below are the results:

Using synchronous calls: Main thread Id is 1
Result on thread 1:"MyFunction with argument '7' lasted 7000 ms on thread 1"
Result on thread 1:"MyFunction with argument '4' lasted 3999 ms on thread 1"
Exception thrown on thread 1:"Exception 'Input string was not 
   in a correct format.' thrown for argument 'bad'.It lasted 5 ms on thread 1"
Result on thread 1:"MyFunction with argument '3' lasted 2999 ms on thread 1"
The UI thread 1 was blocked for 14047 ms

If it was not for the Application.DoEvents()[^], the UI would have frozen while executing the event handler for 14 seconds. I am using DoEvents to try to get a little responsiveness from the UI during the loop and display each method call completion, but waiting seconds for the application to respond still makes a very poor user experience. The tasks are executed sequentially and the duration of the loop will be the sum of all iterations. While the first two goals set above failed, at least the third that's about notifying the user succeeded. Certainly we could do better than this.

Using delegates asynchronously in parallel with the UI thread

It is clear by now that having a responsive UI requires having some extra threads [^] involved that would allow the UI thread to offload lengthy operations to other threads and spend as little time as possible in the event handler. Also, the non-UI threads should be able to post back to the main UI thread when needed. The oldest asynchronous programming pattern that showed up in the .NET 1.1 days was using delegates[^]. It did not offer much more than the underlying threading model[^] except some compiler support[^]. Probably the most visible weakness is the lack of call marshalling from non-UI to UI threads. Due to the affinity of the UI elements to their own thread, you would have to take care of it yourself to avoid InvalidOperatinException being thrown randomly. To do it, I have relied on Control.Invoke [^] as shown in this helper method:

void UpdateTextAsync(string format, int threadId, object txt)
{
    this.Invoke(new Action(() => 
        _textBox.Text += string.Format(format, threadId, txt)));
}

Within the UI handler, the goal is to set the 'heavy' work on one or more background threads and return as quickly as possible. That's why in the code below BeginInvoke is spawning a thread behind the curtain and posting back when all the work was done in a callback[^]. Unfortunately, for a Delegate, even the callback is executed on the same thread that has just run the delegate's handler, as we will see in the results below. Hence there is no way to call UI elements safely without some helper like UpdateTextAsync.

private void _buttonDelegates_Click(object sender, EventArgs e)
{
    DisableUIControls(true);
    _textBox.Text = string.Format("Using delegates: Main thread Id is {0}\r\n", 
                            Thread.CurrentThread.ManagedThreadId);
    Stopwatch duration = Stopwatch.StartNew();
    Action asyncMonitor = new Action(monitorActionsAsync);
    AsyncCallback finalCallback = new AsyncCallback((IAsyncResult ar) =>
    {
        try
        {
            UpdateTextAsync("Final callback from " + 
               "thread {0}   :\"Everything lasted {1} ms\"\r\n",
               Thread.CurrentThread.ManagedThreadId,
               duration.ElapsedMilliseconds.ToString());
        }
        catch (Exception ex)
        {

            UpdateTextAsync("Final callback from " + 
              "thread {0}    :\"Exception thrown in finalCallback :{1}\"\r\n",
              Thread.CurrentThread.ManagedThreadId, ex.Message );
        }
        finally
        {
            this.Invoke(new Action(() => DisableUIControls(false)));
            //DisableUIControls(false); could throw InvalidOperatinException
        }

    });
    asyncMonitor.BeginInvoke(finalCallback, duration);
    _textBox.Text += string.Format("The UI thread {0} was blocked for {1} ms\r\n",
                            Thread.CurrentThread.ManagedThreadId, 
                            duration.ElapsedMilliseconds);        
}

While the handler shown above wastes very little time on its UI thread, the real hard work is done by some of the threads executing in the background. In the code below, notice that blocking this thread would have no impact on the UI responsiveness because it's not the UI thread. This thread would merely wait until all of the threads it spawned completed its activity, in this case executing MyFunction.

void monitorActionsAsync()
{
    Func<string, string> mydelegate = new Func<string, string>(MyFunction);

    AsyncCallback asyncCallback = new AsyncCallback((IAsyncResult ar) =>
        {
            try
            {
                // Retrieve the delegate.
                Func<string, string> caller = 
                  (Func<string, string>)((AsyncResult)ar).AsyncDelegate;
                UpdateTextAsync("Result callback from thread {0}:\"{1}\"\r\n",
                                Thread.CurrentThread.ManagedThreadId, 
                                caller.EndInvoke(ar)); 
            }
            catch (Exception ex)
            {
                UpdateTextAsync("Result callback from thread {0}:\"{1}\"\r\n",
                    Thread.CurrentThread.ManagedThreadId,
                    "Exception thrown in asyncCallback :" + ex.Message);
            }
            finally
            {
                ar.AsyncWaitHandle.Close();   
            }
        });
    //thread spawning
    IEnumerable<WaitHandle> waitHandles = _myArguments.Select
        (
        arg=>mydelegate.BeginInvoke(arg, asyncCallback, null).AsyncWaitHandle
        );
    WaitHandle.WaitAll(waitHandles.ToArray());
}

I think that most people will agree that this code is quite verbose and complicated, and while the anonymous delegates and lambda expressions helped a bit, they could not save the day to make the code easier to read and understand. Here are the results:

Using delegates: Main thread Id is 1
The UI thread 1 was blocked for 4 ms
Result callback from thread 6:"Exception thrown in 
  asyncCallback :Exception 'Input string was not in a correct format.' 
  thrown for argument 'bad'.It lasted 6 ms on thread 6"
Result callback from thread 5:"MyFunction with argument '4' lasted 3999 ms on thread 5"
Result callback from thread 6:"MyFunction with argument '3' lasted 2999 ms on thread 6"
Result callback from thread 4:"MyFunction with argument '7' lasted 6999 ms on thread 4"
Final callback from thread 3 :"Everything lasted 7029 ms"

The first thing to notice is how little time (4 ms) the event handler spent running on the UI thread. That is what made the UI responsive while the threads spawned were executing in parallel with it. The second observation is that all the delegates executed at the same time on different threads (the same as the callbacks) and we got the final notification when the lengthiest thread completed (after 7 seconds). It's safe to say we hit all the goals we set to achieve before. Should you need more optimization concerning the number of threads used, you can change monitorActionsAsync to execute a call to MyFunction synchronously after thread spawning and before WaitHandle.WaitAll, while executing the rest just as before on their own threads started by BeginInvoke. But this is just an exercise left for your own enjoyment.

Using the Event-based Asynchronous Pattern to achieve UI parallelism

While you have just seen previously that it is possible to overcome thread affinity communication constraints using Control.Invoke [^], since .NET 2.0, Microsoft provided the BackgroundWorker [^] to deal with these issues. In addition, new features make this pattern more attractive for developing UI applications.

The BackgoundWorker [^] does not have functionality to communicate with other background workers but only with the UI thread. Hence unlike Delegate's callbacks, its exposed events are executed on the UI thread and we should not be concerned about direct calls to UI elements in our implementation. In order to get a notification that all working threads have completed, you can try querying the IsBusy [^] property on each worker from a timer. I found that it is easier to use a decrementing counter in the RunWorkerCompleted Event [^] to get to the same result. Below is my implementation:

private void _buttonBgWorkers_Click(object sender, EventArgs e)
{
    DisableUIControls(true);
    _textBox.Text = string.Format(
                    "Using background workers: Main thread Id is {0}\r\n", 
                    Thread.CurrentThread.ManagedThreadId);
    int BusyWorkersCount = _myArguments.Length;
    Stopwatch duration = Stopwatch.StartNew();
    foreach (string arg in _myArguments)
    {
        BackgroundWorker bw = new BackgroundWorker();
        bw.DoWork += new DoWorkEventHandler((object snd, DoWorkEventArgs ev) =>
        {
            ev.Result = MyFunction(ev.Argument as string);
        });
        string storedstr = arg;
        bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(
        (object snd, RunWorkerCompletedEventArgs ev) =>
        {
            if (ev.Error != null)
            {
                // First, handle the case where an exception was thrown.
                _textBox.Text += string.Format(
                "Exception postback on thread {0}:\"{1}\"\r\n",
                    Thread.CurrentThread.ManagedThreadId, ev.Error.Message);
            }
            else
            {
                // Finally, handle the case where the operation succeeded.
                _textBox.Text += string.Format(
                "Result postback on thread {0}:\"{1}\"\r\n",
                    Thread.CurrentThread.ManagedThreadId, ev.Result);
            }
            if (--BusyWorkersCount == 0)
            {//when last thread finished, show it
                duration.Stop();
                _textBox.Text += string.Format(
                "Final postback on thread {0}: Everything lasted {1} ms\r\n",
                        Thread.CurrentThread.ManagedThreadId, 
                        duration.ElapsedMilliseconds);
                DisableUIControls(false);
            }


        });
        bw.RunWorkerAsync(arg);
    }
    _textBox.Text += string.Format("The UI thread {0} was blocked for {1} ms\r\n",
                     Thread.CurrentThread.ManagedThreadId, duration.ElapsedMilliseconds);
}

When compared to the Asynchronous Delegates implementation, you can see that the code is easier to understand and how lambda expressions helped make the code so compact by sharing the BusyWorkersCount among different threads. In all fairness, I have to say that this algorithm is also quite different from the previous approach. The results of this operation are shown below:

Using background workers: Main thread Id is 1
The UI thread 1 was blocked for 7 ms
Exception postback on thread 1:"Exception 'Input string was not 
   in a correct format.' thrown for argument 'bad'.It lasted 5 ms on thread 5"
Result postback on thread 1:"MyFunction with argument '4' lasted 3999 ms on thread 4"
Result postback on thread 1:"MyFunction with argument '3' lasted 2999 ms on thread 5"
Result postback on thread 1:"MyFunction with argument '7' lasted 6999 ms on thread 3"
Final postback on thread 1: Everything lasted 7009 ms

The results show that we achieved what we set up to do initially: the event handler spent only 7 ms running on the UI thread and that is what made for a brisk UI response while the background workers were executing in parallel with it. Also, all the workers executed at the same time on different threads and we got the final notification when the longest action completed (after 7 seconds). This implementation appears to perform as good as the previous one.

Using TPL for UI concurrency

.NET 4.0 introduced the Task Parallel Library [^] (TPL) where almost everything revolves around the concept of Task [^]. That is likely to become the preferred way of dealing with parallelism and concurrency going forward. Hence, I have created this helper function that wraps MyFunction into a task oriented method that will be used in this article for the TPL and async syntax implementations as a short hand notation:

Task<string> MyFunctionAsync(string arg)
{
    return Task.Factory.StartNew(() => MyFunction(arg));
}

The code below uses the ContinueWith [^] and ContinueWhenAll [^] methods to execute the tasks in parallel and then to notify the UI thread of completion.

private void _buttonTasks_Click(object sender, EventArgs e)
{
    DisableUIControls(true);
    _textBox.Text = string.Format("Using Tasks: Main thread Id " + 
       "is {0}\r\n", Thread.CurrentThread.ManagedThreadId);
    TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
    Stopwatch duration = Stopwatch.StartNew();

    Task[] tasks = (from arg in _myArguments
                    select MyFunctionAsync(arg)
                        .ContinueWith(resultTask =>
                        {
                            if (resultTask.Exception != null)
                                _textBox.Text += string.Format(
                                  "Exception postback on thread {0}:\"{1}\"\r\n",
                                  Thread.CurrentThread.ManagedThreadId, 
                                  resultTask.Exception.InnerException.Message);
                            else
                                _textBox.Text += string.Format(
                                  "Result postback on thread {0}:\"{1}\"\r\n",
                                  Thread.CurrentThread.ManagedThreadId, resultTask.Result);
                        },
                        uiScheduler)).ToArray();


    Task.Factory.ContinueWhenAll(tasks,
            (Task[] result) =>
            {
                duration.Stop();
                _textBox.Text += string.Format("Final postback on " + 
                    "thread {0}:Everything lasted {1} ms\r\n",
                    Thread.CurrentThread.ManagedThreadId, 
                    duration.ElapsedMilliseconds);
                DisableUIControls(false);
            }, CancellationToken.None, TaskContinuationOptions.None, uiScheduler);

    _textBox.Text += string.Format("The UI thread {0} was blocked for {1} ms\r\n",
                Thread.CurrentThread.ManagedThreadId, duration.ElapsedMilliseconds);
}

Notice that what makes the transition look seamless between the non-UI and the UI thread is the TaskScheduler uiScheduler object that shows up as an argument in both the ContinueWith and ContinueWhenAll calls as the "magic" ingredient. Contrast this code with the above Asynchronous Delegates implementation that was based on a similar mechanism, and you will see a great improvement in code expressiveness. Running this code yields the following results:

Using Tasks: Main thread Id is 1
The UI thread 1 was blocked for 18 ms
Exception postback on thread 1:"Exception 'Input string was not in 
   a correct format.' thrown for argument 'bad'.It lasted 5 ms on thread 5"
Result postback on thread 1:"MyFunction with argument '4' lasted 3999 ms on thread 4"
Result postback on thread 1:"MyFunction with argument '3' lasted 2999 ms on thread 5"
Result postback on thread 1:"MyFunction with argument '7' lasted 6999 ms on thread 3"
Final postback on thread 1:Everything lasted 7018 ms

Again, the results show that the event handler spent only a few milliseconds (18 ms) on the UI thread, ensuring the UI responsiveness and the background tasks were executing in parallel with it. It appears that usually a different thread is associated with each task, but for the exception thrown when it got reused. The final notification came after a task created exclusively for it posted back on the main UI thread. As with the previous patterns, we achieved all the goals we needed.

Using the new 'async' syntax in the UI context

As I have mentioned before, Visual Studio 2010 does not support the new asynchronous language syntax 'out of the box', but its successor most likely will. In the meantime, if you need to take advantage of Asynchronous Programming [^] you have to download and install Visual Studio Async CTP (SP1 Refresh) [^]. I remind you that this article is not an introduction to .NET asynchrony [^]. For that, you already have some nice articles on this topic in the MSDN magazine [^] or here on CodeProject [^], and surely more will come.

How to easily make your synchronous code 'asynchronous'

One of its main advertised strengths is the convenience of transforming synchronous code into asynchronous [^]. These new asynchronous language features are merely syntactical sugar built on top of the existing Task Parallel Library. I have already taken care of the MyFunction to MyFunctionAsync conversion before, so let's do the 'async migration' on our original relevant synchronous code, in a side by side fashion, underlining the code changes needed.

Synchronous code Asynchronous code
void _buttonSynch_Click(object sender, EventArgs e)
{    
    DisableUIControls(true);
    _textBox.Text = string.Format(
      "Using synchroneous calls: " + 
      "Main thread Id is {0}\r\n", 
      Thread.CurrentThread.ManagedThreadId);
    Stopwatch duration = Stopwatch.StartNew();
    foreach (string arg in _myArguments)
    {
      try
      {
        _textBox.Text += string.Format(
          "Result on thread {0}:\"{1}\"\r\n",
          Thread.CurrentThread.ManagedThreadId, 
          MyFunction(arg));
      }
      catch (Exception ex)
      {
        _textBox.Text += string.Format(
         "Exception thrown on thread " + 
         "{0}:\"{1}\"\r\n", 
         Thread.CurrentThread.ManagedThreadId, 
         ex.Message);
      }
   }
   duration.Stop();
   _textBox.Text += string.Format(
    "The UI thread {0} was " + 
    "blocked for {1} ms\r\n",
    Thread.CurrentThread.ManagedThreadId, 
    duration.ElapsedMilliseconds);
   DisableUIControls(false);
}
async void ExecuteAsyncSerial1()
{
    DisableUIControls(true);
    _textBox.Text = string.Format(
      "Using async serial 1: " + 
      "Main thread Id is {0}\r\n",
      Thread.CurrentThread.ManagedThreadId);
    Stopwatch duration = Stopwatch.StartNew();
    foreach (string arg in _myArguments)
    {
        try
        {
            _textBox.Text += string.Format(
              "Result on thread {0}:\"{1}\"\r\n",
              Thread.CurrentThread.ManagedThreadId, 
              await MyFunctionAsync(arg););
        }
        catch (Exception ex)
        {
            _textBox.Text += string.Format(
              "Exception thrown on thread " + 
              "{0}:\"{1}\"\r\n",
              Thread.CurrentThread.ManagedThreadId, 
              ex.Message);
        }

   }
   duration.Stop();
   _textBox.Text += string.Format(
    "Final callback on " + 
    "thread {0} after {1} ms\r\n",
    Thread.CurrentThread.ManagedThreadId, 
    duration.ElapsedMilliseconds);
   DisableUIControls(false);
}

To have the 'conversion' complete, do not forget to prefix the method containing this new code with the async keyword. Next, let's see how can we call it from the window handler and show the rest of all the relevant calling code:

private void _buttonAsyncSerial1_Click(object sender, EventArgs e)
{  
    Stopwatch duration = Stopwatch.StartNew();
    ExecuteAsyncSerial1();//lasts only miliseconds
    _textBox.Text += string.Format(
            "The UI thread {0} was blocked for {1} ms\r\n",
                Thread.CurrentThread.ManagedThreadId, 
            duration.ElapsedMilliseconds);
}

At this point, it might appear for some of you that the 'async' syntax is a 'slam dunk' for any synchronous to asynchronous conversion. Let's check the results we have got by running this code:

Using async serial 1: Main thread Id is 1
The UI thread 1 was blocked for 18 ms
Result on thread 1:"MyFunction with argument '7' lasted 6999 ms on thread 3"
Result on thread 1:"MyFunction with argument '4' lasted 3999 ms on thread 4"
Exception thrown on thread 1:"Exception 'Input string was not in a correct format.' 
          thrown for argument 'bad'.It lasted 5 ms on thread 4"
Result on thread 1:"MyFunction with argument '3' lasted 2999 ms on thread 3"
Final callback on thread 1 after 14022 ms

The UI is still responsive because the handler has executed on the UI thread very fast (18 ms), and we get the notification of completion from each individual Task spawned by the async pattern. As a bonus, the non UI to UI marshalling was totally transparent unlike the previous TPL implementation. But looking deeper at the execution times, you will notice that it took longer than expected to complete all the tasks. It becomes clear that while the tasks were executing in parallel to the UI thread, they were executing serially relative to each other. And that's despite the fact that they were clearly using different threads as their ThreadId (3 and 4) shows. That's not a good situation given the fact that threads are expensive resources and we did not use their entire potential. If you like a gasoline engine analogy, it sounds like this: Instead of having all cylinders firing on each cycle, we ended up with only a single random cylinder firing on every cycle.... Not very effective, a single cylinder could do the same job! A more efficient implementation would be to have only one task executing all the calls asynchronously as shown below. This means merely replacing ExecuteAsyncSerial1 with ExecuteAsyncSerial2 shown below in the UI handler.

async void ExecuteAsyncSerial2()
{
    DisableUIControls(true);
    _textBox.Text = string.Format(
           "Using async serial 2: Main thread Id is {0}\r\n",
            Thread.CurrentThread.ManagedThreadId);
    Stopwatch duration = Stopwatch.StartNew();
    Task tsk = Task.Factory.StartNew(() =>
    {
        foreach (string arg in _myArguments)
        {
            try
            {

                string result = MyFunction(arg);
                _textBox.Text += string.Format(
                "Result on thread {0}:\"{1}\"\r\n",
                                 Thread.CurrentThread.ManagedThreadId, 
                 result);
            }
            catch (Exception ex)
            {
                _textBox.Text += string.Format(
                "Exception thrown on thread {0}:\"{1}\"\r\n",
                                Thread.CurrentThread.ManagedThreadId, 
                ex.Message);
            }

        }
    });
    await tsk;
    duration.Stop();
    _textBox.Text += string.Format(
               "Final callback on thread {0} after {1} ms\r\n",
                            Thread.CurrentThread.ManagedThreadId, 
                duration.ElapsedMilliseconds);
    DisableUIControls(false);
}

Notice that we went back from MyFunctionAsync to MyFunction, but still the second 'async' code conversion is not as 'easy' as the previous one. However, the results look similar and show that we won't create unnecessary threads this time:

The UI thread 1 was blocked for 15 ms
Result on thread 3:"MyFunction with argument '7' lasted 6999 ms on thread 3"
Result on thread 3:"MyFunction with argument '4' lasted 3999 ms on thread 3"
Exception thrown on thread 3:"Exception 'Input string was not in 
          a correct format.' thrown for argument 'bad'.It lasted 5 ms on thread 3"
Result on thread 3:"MyFunction with argument '3' lasted 2999 ms on thread 3"
Final callback on thread 1 after 14013 ms

While the upcoming 'async' syntax seems easier to use than TPL classes, we clearly came short on the second goal we set previously: "The code should execute its actions in parallel and independently". If you can think of an 'easy' way of hitting that goal, please let me know. So the next question is, "is this new syntax good enough to solve more complex asynchronous scenarios"?

How to start multiple parallel tasks and await them

It turns out you can still 'await' and achieve all of the original goals, but the code won't resemble much of the original synchronous code. Maybe somebody will prove me wrong on this statement, but in the meantime, here are my possible solutions to this exercise based on the 'async' language features:

private void _buttonAsyncParallel1_Click(object sender, EventArgs e)
{
    DisableUIControls(true);
    _textBox.Text = string.Format(
            "Using async parallel 1: Main thread Id is {0}\r\n",
                    Thread.CurrentThread.ManagedThreadId);
    Stopwatch duration = Stopwatch.StartNew();
    ExecuteAsyncParallel1();//or ExecuteAsyncParallel2() will last only miliseconds
    _textBox.Text += string.Format(
            "The UI thread {0} was blocked for {1} ms\r\n",
                        Thread.CurrentThread.ManagedThreadId, 
            duration.ElapsedMilliseconds);
}


async void ExecuteAsyncParallel1()
{
    Stopwatch duration = Stopwatch.StartNew();
    //without .ToArray() or .ToList() will wait after each task
    List<Task<string>> tasks = _myArguments.Select(
                                arg => MyFunctionAsync(arg)).ToList();
    // spawn all the threads
    tasks.ForEach(async (task) =>
        {
            try
            {
                _textBox.Text += string.Format(
                                "Result on thread {0}:\"{1}\"\r\n",
                                Thread.CurrentThread.ManagedThreadId, 
                                await task);

            }
            catch (Exception ex)
            {
                _textBox.Text += string.Format(
                            "Exception thrown on thread {0}:\"{1}\"\r\n",
                            Thread.CurrentThread.ManagedThreadId, 
                            ex.Message);
            }
        });
    //wait to finish all tasks
    try
    {
        await TaskEx.WhenAll(tasks);
    }
    catch //discard the exception(s)
    {
    }
    duration.Stop();
    _textBox.Text += string.Format("Final callback on thread {0} after {1} ms\r\n",
                            Thread.CurrentThread.ManagedThreadId, 
                            duration.ElapsedMilliseconds);
    DisableUIControls(false);
}

The first approach is to create and await each task individually and than await again the completion for all of them. This is very similar with my previous Task Parallel Library implementation. Now it's probably a good opportunity to appreciate the 'async' syntax again in all its glory as this code looks simpler than its TPL counterpart. Also, we can safely assume that the compiler automatically generated code behind to create the Task continuations and non UI to UI context switching. You might have noticed the use of the class TaskEx in the above code. That class is likely to be available only for CTP and the next version of .NET will drop it, and all its static methods will end up in the Task class. Below are the results:

Using async parallel 1: Main thread Id is 1
The UI thread 1 was blocked for 42 ms
Exception thrown on thread 1:"Exception 'Input string was not in a correct 
   format.' thrown for argument 'bad'.It lasted 6 ms on thread 5"
Result on thread 1:"MyFunction with argument '4' lasted 3999 ms on thread 4"
Result on thread 1:"MyFunction with argument '3' lasted 3000 ms on thread 5"
Result on thread 1:"MyFunction with argument '7' lasted 6999 ms on thread 3"
Final callback on thread 1 after 7022 ms

As you can see, we cured the ills of my previous asynchronous implementations by executing all the tasks really in parallel this time, so all the goals I set originally were achieved.

But this solution is not unique. I found another one, and probably some astute guy/gal will come up with other ideas. This time I am removing the tasks from a list as they complete and notify the UI thread.

async void ExecuteAsyncParallel2()
{
    Stopwatch duration = Stopwatch.StartNew();
    List<Task<string>> tasks = _myArguments.Select(
                                      arg => MyFunctionAsync(arg)).ToList();
    //eliminate threads that have completed
    while (tasks.Count > 0)
    {
        Task<string> tsk = null;
        try
        {
            tsk = await TaskEx.WhenAny(tasks);
            _textBox.Text += string.Format(
                            "Result on thread {0}:\"{1}\"\r\n",
                            Thread.CurrentThread.ManagedThreadId, 
                            await tsk);
        }
        catch (Exception ex)
        {

            _textBox.Text += string.Format(
                            "Exception thrown on thread {0}:\"{1}\"\r\n",
                            Thread.CurrentThread.ManagedThreadId, 
                            ex.Message);
        }
        finally
        {
            tasks.Remove(tsk);
        }

    }
    duration.Stop();
    _textBox.Text += string.Format(
                    "Final callback on thread {0} after {1} ms\r\n",
                        Thread.CurrentThread.ManagedThreadId, 
                        duration.ElapsedMilliseconds);
    DisableUIControls(false);
    return;
}

Just replace ExecuteAsyncParallel1() with this new ExecuteAsyncParallel2() in the call from the UI handler and the results will look like below:

Using async parallel 2: Main thread Id is 1
The UI thread 1 was blocked for 50 ms
Exception thrown on thread 1:"Exception 'Input string was not in a correct 
   format.' thrown for argument 'bad'.It lasted 6 ms on thread 5"
Result on thread 1:"MyFunction with argument '4' lasted 3999 ms on thread 3"
Result on thread 1:"MyFunction with argument '3' lasted 2999 ms on thread 5"
Result on thread 1:"MyFunction with argument '7' lasted 6999 ms on thread 4"
Final callback on thread 1 after 7019 ms

As expected, like the latest implementation, it hits all the 3 goals I have set initially but it is even farther apart from the original synchronous code. Also, this task removal implementation looks very similar with the decrementing counter you saw in my previous BackgroundWorker implementation... Old ideas become new ideas. I'm no language designer, but I wish that Microsoft would provide syntax support for something like AwaitAny and AwaitAll, to have a complete developer story and symmetry with the existing Task "WhenXXX" methods, WhenAny [^] and WhenAll [^].

Points of interest

I hope this article has shown some of the joys and pitfalls of using various techniques applied to make UI applications more responsive. It seems that .NET provides many ways to tackle concurrency, may be too many some might say, and who knows what future versions will bring. I have used WinForms but reproducing the same results in WPF should be easy enough since delegate, BackgroundWorker, TPL, or the new asynchronous language syntax do not depend on a certain UI technology. I have included the current AsyncCtpLibrary.dll that would allow you to run the executable on .NET 4.0 runtime, but for a Visual Studio 2010 build, you would still need the whole CTP download [^]. If you use one of the VS2010 successors, you can remove this library from the project's references, get rid of the CTP artifacts like TaskEx, and use references to assemblies the newer .NET Framework provides instead.

History

  • Version 1.0: This is the first version.
  • Version 1.1: Added the Asynchronous Delegates topic and supporting code.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here