The .NET platform offers several asynchronous programming patterns. This article examines the full evolution of this important technique as it has manifested during different periods. By examining past patterns, the goals and mechanisms of the modern Task-based pattern using async and await can be better understood. For many, this historical perspective will provide a firmer grounding in modern asynchronous programming techniques, and for others, it will aid in the maintenance or revision of legacy code.

Throughout the article, the same simple application demonstrates how to use each of the asynchronous patterns. In addition, each asynchronous pattern is paired with the modern MVVM architecture pattern. Each example implements a start slow process button, a slow process with a progress indicator, and a cancel slow process button to halt the slow process before it completes. The asynchronous patterns listed in Table 1 are illustrated.

Introducing the SlowProcess Application

Listing 1 shows the XAML for the MainWindow for the SlowProcess application.

Listing 1: XAML Showing the MainWindow

<Window x:Class="DirectThreadAsync.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation";
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml";
        Title="Direct Thread Async"
        Height="600"
        Width  ="300">
    <Grid>
        <ScrollViewer VerticalScrollBarVisibility="Auto">
            <StackPanel TextElement.FontSize="20"
                        Name="MainStackPanel"
                        ScrollViewer.VerticalScrollBarVisibility="Auto"
                        VerticalAlignment="Top">
                <Button x:Name="StartSlowProcessButton"
                        Click="StartSlowProcessButton_Click"
                        Background="LightGreen"
                        Padding="3"
                        BorderThickness="2"
                        BorderBrush="Black">
                  _Start New Slow Process
                </Button>
            </StackPanel>
        </ScrollViewer>
    </Grid>
</Window>

The slow process is a method that counts to five in one-second increments. In reality, a slow process might be a CPU- or I/O-intensive operation. The MainWindow hosts the MainStackPanel. MainStackPanel provides a StartSlowProcessButton and is where the new SlowProcessView UserControls will be started and added to the UI. Listing 2 shows the XAML for the SlowProcessView UserControl, which is where you show a start message, a progress indicator, and a result message for each invocation of the SlowProcess.

Listing 2: XAML Showing the SlowProcessView UserControl

<UserControl x:Class="DirectThreadAsync.SlowProcessView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation";
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml";
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006";
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008";>
    <Grid>
        <Border BorderBrush="Black"
                BorderThickness="2,0,2,2">
            <StackPanel>
                <TextBlock x:Name="StartedText"
                           Text="{Binding StartData}"
                           HorizontalAlignment="Center" />
                <TextBlock x:Name="ProgressText"
                           Text="{Binding ProgressData}"
                           FontSize="30"
                           FontWeight="Bold"
                           HorizontalAlignment="Center" />
                <ProgressBar x:Name="ProgressBar"
                             Height="10"
                             Width="250"
                             Minimum="0"
                             Maximum="5"
                             Foreground="Navy"
                             Value="{Binding ProgressData}"/>
                <TextBlock x:Name="ResultText"
                           HorizontalAlignment="Center" />
                <Button x:Name="CancelSlowProcessButton"
                        Click="CancelSlowProcessButton_Click"
                        Background="LightCoral"
                        BorderThickness="2"
                        BorderBrush="Black"
                        Margin="8"
                        Padding="3"
                        Width="200">
                  _Cancel Slow Process
                </Button>
            </StackPanel>
        </Border>
    </Grid>
</UserControl>

In addition, the SlowProcessView provides a cancel button, which allows the user to interrupt the SlowProcess at any time and stops it from completing. The result of adding two SlowProcessViews, with one canceled and one completed, is shown in Figure 1.

Figure 1: The Direct Thread Async SlowProcess application running with one canceled process and one completed process
Figure 1: The Direct Thread Async SlowProcess application running with one canceled process and one completed process

Asynchronous MVVM

The other class involved in each pattern is the SlowProcessViewModel. The implementation included in the examples exhibits a view-first implementation of the MVVM pattern. The view-first approach was chosen primarily for illustration purposes here. First, it makes more intuitive sense when the click handlers and UI update code are first seen running on the Main/UI thread. As you will see, the running thread and an object's scope of responsibility are not always a clean match, so explaining the transitions is easier when the UI event (a click) is first handled by a UI object (a click handler on a UI control) on the Main/UI thread. However, the Main/UI thread runs ViewModel code at different times, and that's perfectly fine, so long as the processing code does not block the Main/UI thread for more than 50 milliseconds.

It's noteworthy that all of these examples could have been written using Commands to handle the user clicks directly on the ViewModel. The thread affinity requirements of the UI are the same, but there are additional complexities when writing asynchronous commands, which are outside the scope of this article.

In the Beginning, There Was the ThreadPool

The following user steps show how the UI results shown in Figure 1 were produced:

  1. Click Start for the first process
  2. Click Start for the second process
  3. Click Cancel for the first process
  4. Allow the second process to complete

The above user steps result in the output results shown in Listing 6. Note that the code listings (Listings 3, 4, and 5) for the code that produced the output results precede the output results listing (Listing 6).

Listing 3: DirectThreadAsync's MainWindow Code-behind

namespace DirectThreadAsync
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            var token = StopwatchHelper.StartNew(TimerType.MAIN_TIMER);
            StopwatchHelper.Stop(token);

            InitializeComponent();
        }

        private void StartSlowProcessButton_Click(
            object sender, RoutedEventArgs e)
        {
            var token = StopwatchHelper.StartNew(
            TimerType.START_CLICK_TIMER);

            // Create a new UserControl with a ViewModel
            // and add it to the StackPanel
            var uc = new SlowProcessView();
            uc.ViewModel = new SlowProcessViewModel();
            uc.DataContext = uc.ViewModel;
            MainStackPanel.Children.Add(uc);

            // Put work on a ThreadPool thread (returns immediately
            // with boolean)
            bool isQueued = ThreadPool.QueueUserWorkItem(
                SlowProcessWorkItem, uc);

            StopwatchHelper.Stop(token);
        }

        private void SlowProcessWorkItem(object o)
        {
            var uc = o as SlowProcessView;

            string data = uc.ViewModel.SlowProcess();

            // Use this object's Dispatcher object to schedule
            // the UI update back on the UI thread

            // Use separate method
            uc.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
            new Action<SlowProcessView,string>
            (UpdateResultText), uc, data);
        }

        private void UpdateResultText(SlowProcessView uc, string data)
        {
            var token = StopwatchHelper.StartNew(
                TimerType.UPDATE_RESULT_TIMER);

            uc.ResultText.Text = data;

            StopwatchHelper.Stop(token);
        }
    }
}

Listing 4: DirectThreadAsync's SlowProcessView Code-behind

namespace DirectThreadAsync
{
    public partial class SlowProcessView : UserControl
    {
        public SlowProcessView()
        {
            InitializeComponent();
        }

        public SlowProcessViewModel ViewModel { get; set; }

        private void CancelSlowProcessButton_Click(object sender,
        RoutedEventArgs e)
        {
            var token = StopwatchHelper.StartNew(
                TimerType.CANCEL_CLICK_TIMER);

            ViewModel.IsCanceled = true;

            StopwatchHelper.Stop(token);
        }
    }
}

Listing 5: DirectThreadAsync's SlowProcessViewModel

namespace DirectThreadAsync
{
    public class SlowProcessViewModel : INotifyPropertyChanged
    {
        public string SlowProcess()
        {
            var token = StopwatchHelper.StartNew(TimerType.WORKER_TIMER);

            StartData = "Slow process started.";

            var rv = "";

            for (var i = 0; i <= 5; i++)
            {
                if (IsCanceled)
                {
                    rv = "Slow process canceled.";
                    break;
                }

                Thread.Sleep(TimeSpan.FromSeconds(1));
                ProgressData = i;
            }

            if (rv == String.Empty)
            rv = "Slow process completed.";

            StopwatchHelper.Stop(token);

            return rv;
        }

        private string _startData = "";
        public string StartData
        {
            get
            {
                return _startData;
            }
            set
            {
                _startData = value;
                RaisePropertyChanged("StartData");
            }
        }

        private int _progressData = 0;
        public int ProgressData
        {
            get
            {
                return _progressData;
            }
            set
            {
                _progressData = value;
                RaisePropertyChanged("ProgressData");
            }
        }

        public bool IsCanceled { get; set; }

        #region Implement INotifyPropertyChanged

        public void RaisePropertyChanged(string name)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this,
                    new PropertyChangedEventArgs(name));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

    #endregion

Listing 6: DirectThreadAsync's Output Results

 1 Identify Main Thread (Started):  10
 2 Identify Main Thread (Ending):   10 Elapsed: 5
 3 Start Click Thread (Started):    10
 4 Start Click Thread (Ending):     10 Elapsed: 4
 5 Worker Thread (Started):          6
 6 Start Click Thread (Started):    10
 7 Start Click Thread (Ending):     10 Elapsed: 4
 8 Worker Thread (Started):         11
 9 Cancel Click Thread (Started):   10
10 Cancel Click Thread (Ending):    10 Elapsed: 2
11 Worker Thread (Ending):           6 Elapsed: 4007
12 Update Result Thread (Started):  10
13 Update Result Thread (Ending):   10 Elapsed: 7
14 Worker Thread (Ending):          11 Elapsed: 6032
15 Update Result Thread (Started):  10
16 Update Result Thread (Ending):   10 Elapsed: 32

As shown by the output results, the first and second clicks occurred on the Main/UI thread. On Line 5, the first call to the SlowProcessWorkItem method is put onto a worker thread by the following code, taken from start button's click handler shown in Listing 3:

bool isQueued = ThreadPool.QueueUserWorkItem(
    SlowProcessWorkItem, uc);

Notice how the Start Click Thread ends right away - even before the Worker Thread reports that it has started! The elapsed time shows that the Main/UI thread was available again for new work very quickly - after just four milliseconds, well under the 50 millisecond guideline from Microsoft. This example shows a direct use of the ThreadPool to invoke asynchronous work. As you can see, you pass a work item method (SlowProcessWorkItem) and a state object (uc) to the ThreadPool to be queued for work. Once the work has been successfully queued, a true result is returned. The queuing happens fairly quickly, and then returns, yielding control of the thread back to the calling method. This immediate yielding is what allows the entire Start Click Thread to complete in just four milliseconds.

High Concurrency Versus Low Latency Design Goals

The next important thing to notice is the Cancel Click Thread that's started on Line 9 of the output results (Listing 6). Notice that it's started on the Main/UI thread, proving that the UI was able to respond to user input right away, even while the two asynchronous Worker Threads were still processing their work. This is what asynchronous programming is all about with respect to the UI - namely, a responsive UI. This is a much different design goal than seeking to achieve high throughput, such as what a service that needs to handle many concurrent network requests might need to do. The service's goal would be high throughput and high concurrency, whereas the goal here is low latency and high responsiveness. One consequence is that you can be a bit less concerned about the cost of spinning up, and even blocking, on a worker thread. In contrast to the typical UI scenario, a highly concurrent application needs to be much more cognizant about the cost of consuming threads.

Updating the UI

The SlowProcess application interacts with the UI at three points:

  • Displaying the start status text
  • Updating the progress indicator (the counter)
  • Updating the result status text to indicate if the process completed or was cancelled

As shown in Listing 2, the StartedText and ProgressText properties both use data bindings to obtain their updates, whereas the ResultText property does not. In fact, the ResultText property could have used data binding as well. However, not doing so here provides an opportunity to show what the XAML data binding mechanism is actually doing to help out in marshalling UI setter code to the Main/UI thread when binding to simple properties. Importantly, when binding to collections, such as an ObservableCollection, the binding mechanism isn't as helpful in marshalling property setting code to the Main/UI thread - at least not uniformly across all of the different XAML platforms - and the application programmer must manually ensure that the update code is running on the correct thread before updating a collection.

If you look at Line 5 in the output results (Listing 6), you see that the first Worker Thread is started on thread 6 of the ThreadPool. This output is from the SlowProcessWorkItem method in the MainWindow's code-behind, as shown in Listing 3. That method, in turn, calls the SlowProcess method on the SlowProcessView's ViewModel, shown in Listing 5. Now look at what happens inside of the SlowProcess method, which, you'll remember, is running on thread 6, and not the Main/UI thread. The following code gets run:

ProgressData = i;
.
.
.
private int _progressData = 0;
public int ProgressData
{
    get
    {
        return _progressData;
    }
    set
    {
        _progressData = value;
        RaisePropertyChanged("ProgressData");
    }
}

You'll recall that ProgressText in the SlowProcessView's XAML is bound to ProgressData in the ViewModel. As such, because ProgressData raises the PropertyChanged event of the INotifyPropertyChanged interface in its setter, the XAML framework proceeds to retrieve the updated value of ProgressData and attempts to set ProgressText on the SlowProcessView UserControl. However, the code is currently executing on a non-Main/UI thread - namely, Worker Thread 6. If the XAML framework attempts to set the ProgressText property on the SlowProcessView UserControl (a UI component) in response to the update to PropertyData in the ViewModel using Worker Thread 6, it would violate the thread affinity rule of the XAML frameworks that requires all UI components to be updated exclusively using the Main/UI thread.

So, why does this code work? Well, as it turns out, with respect to simple property bindings, the XAML frameworks are smart enough to realize that they need to set the UI object values on the UI thread, and the frameworks take care of marshalling the update onto the Main/UI thread for us. However, as mentioned above, this isn't true for properties bound to collections, which would result in a NotSupportedException.

Let's take a closer look at what the XAML framework is doing when you update a bound property in the ViewModel. To see what's happening, I'll examine what you need to do if, in fact, the XAML framework wasn't marshalling UI updates back to the Main/UI thread for you. Take a look at the ResultText property in Listing 2. Recall, you're not using data binding to update that property in the UI. So what are you doing?

When the SlowProcess method completes or is canceled, it returns its data value to the SlowProcessWorkItem method. Even though the SlowProcessWorkItem is a method on UI object SlowProcessView, the code is still running on Worker Thread 6. It is perfectly fine for code in a UI object created on the Main/UI thread to execute on a non-Main/UI thread, so long as that code doesn't attempt to update any UI objects. Attempting to access a UI component from a non-Main/UI thread throws an InvalidOperationException with the following error message: "The calling thread cannot access this object because a different thread owns it." The issue arises when you attempt to update the UI object from the Worker Thread. It's invalid to set the ResultText property on the SlowProcessView UI component from the Worker Thread. Note that it's the attempt to set the property on the UI component that causes the issue - not getting or setting the property on the ViewModel.

It's perfectly fine for code in a UI object created on the Main/UI thread to execute on a non-Main/UI thread, so long as that code doesn't attempt to update any UI objects.

However, you're in the View tier and you possess a reference to the SlowProcessView UserControl - uc. The solution here is to use the Dispatcher object that's available to all UI components in a XAML application, including the UserControl, to marshal the setter code onto the correct thread.

And that is exactly what the following code from Listing 3 does:

uc.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
    new Action<SlowProcessView,string>
        (UpdateResultText),
    uc,
    data);

Take a look at Lines 12 and 13 in the output results (Listing 6). The code from UpdateResultText does indeed run on the Main/UI thread. When the ResultText property gets set to the data variable in the UpdateResultText method, the update occurs on the Main/UI thread. This is the same thing that the XAML frameworks are doing for us under the covers when you set a UI object's property - or more precisely, when you set an underlying bound property that results in a UI update from a thread that is not the Main/UI thread.

Finally, the last mechanism to examine is the cancellation mechanism. Here, you are combining MVVM architecture with the early primitive asynchronous mechanism of using the ThreadPool directly to achieve asynchronous behavior. Of course, in the beginning, XAML and data binding were not yet available technologies, so this is, in a sense, a modern twist on the original early mechanism. In the SlowProcess application, you're simply setting an IsCanceled property flag on the SlowProcessViewModel (Listing 5). To detect a cancellation, the SlowProcess work loop checks the value of IsCanceled, and returns from the method when it's appropriate to do so.

The Delegate-based Asynchronous Pattern with IAsyncResult

To examine the delegate-based asynchronous pattern (DAP), repeat the same user steps used for the Direct Thread asynchronous pattern:

  1. Click Start for the first process.
  2. Click Start for the second process.
  3. Click Cancel for the first process.
  4. Allow the second process to complete.

The output results now look like those shown in Listing 10. The UI results are shown in Figure 2.

Figure 2      : The delegate-based Async SlowProcess application running with one canceled process and one completed process
Figure 2 : The delegate-based Async SlowProcess application running with one canceled process and one completed process

The DAP is very interesting because it really sets the stage for the modern task-based asynchronous pattern. In fact, the Task class implements the same IAsyncResult interface that the DAP uses in order to provide its functionality. As you will see, these same fundamental mechanisms are still in use, but a lot of complexity is now handled for the application programmer automatically when using the task-based asynchronous pattern with async and await.

How the Delegate-based Asynchronous Pattern Works

So, what's happening here? Well, beginning with .NET 1.0, delegates facilitated asynchronous programming. Using the IAsyncResult interface and the AsyncResult concrete class that implements that interface, the application programmer could implement sophisticated thread synchronization techniques. No longer was the programmer limited to the simple fire-and-forget model of the direct use of the ThreadPool. With the DAP, the calling thread could now store a reference to a “handle” on the asynchronous operation that it just invoked, which then allowed the code on the calling thread to track the asynchronous operation's progress, or perhaps, respond to its results.

Let's see how this works. In Listing 7, the SlowProcessDelegate delegate is declared that matches the signature of the method you want to invoke. Note, this is not the callback continuation method, but the initial asynchronous operation method.

public delegate string SlowProcessDelegate();

SlowProcessDelegate does not take any parameters and it returns a string.

As shown in the StartSlowProcessButton_Click click handler in Listing 7, a new instance of SlowProcessDelegate is assigned an invocation method that matches its signature - in the example, that method is the SlowProcess method on the ViewModel. Then, BeginInvoke is called on the delegate instance in order to invoke that method asynchronously.

Listing 7: DelegateBasedAsync's MainWindow Code-behind

namespace DelegateBasedAsync
{
    public delegate string SlowProcessDelegate();

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            var token =
                StopwatchHelper.StartNew(TimerType.MAIN_TIMER);
            StopwatchHelper.Stop(token);

            InitializeComponent();
        }

        private void StartSlowProcessButton_Click(object sender,
            RoutedEventArgs e)
        {
            var clickToken = StopwatchHelper.StartNew(
                TimerType.START_CLICK_TIMER);

            // Create new UserControl and add it to the StackPanel
            var uc = new SlowProcessView();
            uc.ViewModel = new SlowProcessViewModel();
            uc.DataContext = uc.ViewModel;
            MainStackPanel.Children.Add(uc);

            SlowProcessDelegate spd = uc.ViewModel.SlowProcess;
            var asyncRes = (AsyncResult)spd.BeginInvoke(
                EndSlowProcessCallbackMethod, uc);

            var waitToken = 
                StopwatchHelper.StartNew(TimerType.WAIT_TIMER);

            // This thread waits for another thread to complete its
            // work - giving it a maximum of 1 second to complete
            asyncRes.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(1));
            StopwatchHelper.Stop(waitToken);

            StopwatchHelper.Stop(clickToken);
        }

        private void EndSlowProcessCallbackMethod(IAsyncResult ar)
        {
            var token = 
                StopwatchHelper.StartNew(TimerType.CALLBACK_TIMER);

            AsyncResult asyncRes = (AsyncResult)ar;
            SlowProcessDelegate spd =
                (SlowProcessDelegate)asyncRes.AsyncDelegate;
            var uc = (SlowProcessView)asyncRes.AsyncState;

            // Deterministically cleanup the WaitHandle for anyone
            // referencing it and retrieve the result data
            // produced in spd.BeginInvoke
            string data = spd.EndInvoke(asyncRes);
            uc.Dispatcher.BeginInvoke(
                System.Windows.Threading.DispatcherPriority.Normal,
                new Action<SlowProcessView,string>
                    (UpdateResultText),
                    uc,
                    data);

            StopwatchHelper.Stop(token);
        }

        private void UpdateResultText(SlowProcessView uc,
            string data)
        {
            var token = StopwatchHelper.StartNew(
                TimerType.UPDATE_RESULT_TIMER);

            uc.ResultText.Text = data;

            StopwatchHelper.Stop(token);
        }
    }
}

Notice that BeginInvoke is passed a user callback or continuation method (EndSlowProcessCallbackMethod) and a user state object (uc).

SlowProcessDelegate spd = uc.ViewModel.SlowProcess;

var asyncRes = (AsyncResult)
    spd.BeginInvoke(EndSlowProcessCallbackMethod, uc);

Listing 8: DelegateBasedAsync's SlowProcessView Code-behind

namespace DelegateBasedAsync
{
    // Interaction logic for SlowProcessView.xaml
    public partial class SlowProcessView : UserControl
    {
        public SlowProcessView()
        {
            InitializeComponent();
        }

        public SlowProcessViewModel ViewModel { get; set; }

        private void CancelSlowProcessButton_Click(object sender,
            RoutedEventArgs e)
        {
            var token =
                StopwatchHelper.StartNew(
                TimerType.CANCEL_CLICK_TIMER);

            ViewModel.IsCanceled = true;

            StopwatchHelper.Stop(token);
        }
    }
}

Listing 9: DelegateBasedAsync's SlowProcessViewModel

namespace DelegateBasedAsync
    {
    public class SlowProcessViewModel : INotifyPropertyChanged
    {

        public string SlowProcess()
        {
            var token =
                StopwatchHelper.StartNew(TimerType.WORKER_TIMER);

            StartData = "Slow process started.";

            var rv = "";

            for (var i = 0; i <= 5; i++)
            {
                if (IsCanceled)
                {
                    rv = "Slow process canceled.";
                    break;
                }

                Thread.Sleep(TimeSpan.FromSeconds(1));
                ProgressData = i;
            }

            if (rv == String.Empty)
            rv = "Slow process completed.";

            StopwatchHelper.Stop(token);

            return rv;
        }

        private string _startData = "";
        public string StartData
        {
            get
            {
                return _startData;
            }
            set
            {
                _startData = value;
                RaisePropertyChanged("StartData");
            }
        }

        private int _progressData = 0;
        public int ProgressData
        {
            get
            {
                return _progressData;
            }
            set
            {
                _progressData = value;
                RaisePropertyChanged("ProgressData");
            }
        }

        private bool _isCanceled = false;
        public bool IsCanceled
        {
            get
            {
                return _isCanceled;
            }
            set
            {
                _isCanceled = value;
                RaisePropertyChanged("IsCanceled");
            }
        }

        #region Implement INotifyPropertyChanged

        public void RaisePropertyChanged(string name)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this,
                    new PropertyChangedEventArgs(name));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion
    }
}

Listing 10: DelegateBasedAsync's Output Results

 1 Identify Main Thread (Started):      9
 2 Identify Main Thread (Ending):       9 Elapsed: 5
 3 Start Click Thread (Started):        9
 4 Start Click Thread (Start Waiting):  9
 5 Worker Thread (Started):            10
 6 Start Click Thread (End Waiting):    9 Elapsed: 1008
 7 Start Click Thread (Ending):         9 Elapsed: 1011
 8 Start Click Thread (Started):        9
 9 Start Click Thread (Start Waiting):  9
10 Worker Thread (Started):             11
11 Start Click Thread (End Waiting):     9 Elapsed: 1011
12 Start Click Thread (Ending):          9 Elapsed: 1014
13 Worker Thread (Ending):              10 Elapsed: 4010
14 Callback Thread (Started):           10
15 Callback Thread (Ending):            10 Elapsed: 3
16 Update Result Thread (Started):       9
17 Update Result Thread (Ending):        9 Elapsed: 3
18 Worker Thread (Ending):              11 Elapsed: 6010
19 Callback Thread (Started):           11
20 Callback Thread (Ending):            11 Elapsed: 2
21 Update Result Thread (Started):       9
22 Update Result Thread (Ending):        9 Elapsed: 3

As shown in Line 4 and Line 9 of the output results (Listing 10), this invocation results in the invoked method (uc.ViewModel.SlowProcess) running asynchronously on a new thread. Notice that control is yielded back to the calling thread immediately - in fact, Line 4 and Line 9 of the output results show that the next line of code on the Main/UI thread is run even before the worker thread can start-up and output its start message! BeginInvoke returns a “handle” of the type IAsyncResult to the asynchronous method being invoked, which you then cast to an AsynResult concrete object and assign to a variable.

AsyncResult provides several tools for coordinating processing between the calling thread and the asynchronous operation - much more than a simple Thread reference would provide. Although you use several of those tools as described below, a full description of IAsyncResult is out of scope for this article.

As you can see in Listing 7, the continuation callback method EndSlowProcessCallbackMethod takes a single parameter of type IAsyncResult. As shown in the following snippet, inside that method, you cast the object passed to an AsyncResult; which is the type of the object that the BeginInvoke method passed when calling the continuation method for us:

AsyncResult asyncRes = (AsyncResult)ar;

SlowProcessDelegate spd = (SlowProcessDelegate)asyncRes.AsyncDelegate;

var uc = (SlowProcessView)asyncRes.AsyncState;

You also retrieve the delegate instance that BeginInvoke packaged up for you on the AsyncDelegate property of the AsyncResult object. Lastly, you unpack the user state object that you passed and which BeginInvoke packaged up for you on the AsyncState property of the AsyncResult object. You then cast the state object to the expected SlowProcessView type, which you use below.

The primary takeaway here is to notice the rich continuation facility and asynchronous operation coordination capabilities provided by the IAsyncResult interface and the DAP. Except for the limited support provided by the data-binding mechanism of the XAML frameworks for marshalling updates to UI elements bound to simple properties, managing the synchronization context is left to the application programming and imposes a dependency on UI components for getting access to the Dispatcher in order to schedule updates on the Main/UI thread.

Using the Result from IAsyncResult

It makes sense that something called IAsyncResult should provide a result. As shown in the snippet below, calling EndInvoke on the delegate returns the expected result - in the example, a string. This result is often called the promised result in asynchronous scenarios:

string data = spd.EndInvoke(asyncRes);

EndInvoke also provides a deterministic mechanism for signaling the WaitHandle on the AsyncResult object provided. Therefore, calling EndInvoke signals other areas of code that are attempting to coordinate their work with this process' work and results, allowing them to proceed accordingly.

The last thing for the continuation method to do is display the result in the UI. Just like with the Direct-thread pattern, you use the SlowProcessView's Dispatcher property to access the UI Dispatcher so the assignment code will be scheduled to run back on the Main/UI thread. You then pass a delegate to the UpdateResultText method, as well as the required user state objects, to the Dispatcher's BeginInvoke method, which schedules the work on the Main/UI thread. Lines 16 and 17, as well as Lines 21 and 22 in the output results (Listing 10) confirm that this work is being completed on the Main/UI thread. Again, the XAML frameworks could handle this for you with simple properties not involving collections, but I show it being done manually here to demonstrate the technique; if you were binding to a collection, you'd be required to use the manual technique to ensure that updates were performed on the Main/UI thread.

As an interesting point, notice how you didn't call a Dispatcher.EndInvoke() method to complete the asynchronous call sent to the Dispatcher. As it turns out, the Dispatcher doesn't contain an EndInvoke() method, and therefore, unlike the typical delegate-based asynchronous call scenario, you don't need to call an EndInvoke method when using the Dispatcher to schedule work on the Main/UI thread.

What Are We Waiting For?

The last thing to point out for the DAP are the wait messages appearing on Lines 4 and 6, and Lines 9 and 11 of the output results (Listing 10). Those output messages indicate where the calling thread was asked to wait for the asynchronous operation to return a result. The following code snippet from Listing 7 shows how you can ask the calling thread to halt execution for up to one second while the asynchronous operation proceeds:

asyncRes.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(1));

The code uses the AsyncWaitHandle property on the asyncRes object returned by the BeginInvoke method in order to obtain a WaitHandle. It then uses the WaitOne method to specify that the current thread should halt execution for up to one second while the asynchronous operation associated with the WaitHandle continues processing. If the asynchronous operation signals the WaitHandle that it has completed in less than one second, the waiting thread will then proceed. Otherwise, the waiting thread will proceed after one second, regardless of whether a completion signal is received. In this way, and with other tools provided by the AsyncResult class, asynchronous processing can be coordinated.

Note that the example is completely contrived, and you would never want to block the Main/UI thread for one second in this manner, as this is a blatant violation of the 50-millisecond guideline. However, it does show how one thread's execution can be coordinated with another's, and would more likely be used on a worker thread, where you might not mind the worker thread blocking for some period while you wait on the results of an asynchronous operation fired by that worker thread.

Event-based Asynchronous Pattern with AsyncOperation

The Event-based Asynchronous Pattern (EAP) was introduced with .NET 2.0. The major advance of this pattern over the DAP is that it allows us to pass around a synchronization context. This capability offers several advantages. However, although it's easy to consume by a client simply subscribing to events, the EAP introduces a lot of overhead and complexity for the asynchronous operation author. I'll explore EAP's key mechanisms for completeness, but keep it brief since it is a legacy pattern, and it's unlikely (and not recommended) that you would choose the EAP for future asynchronous processing requirements.

As with the prior examples, the MainWindow and SlowProcessView XAML code shown in Listing 11 and Listing 12 is the same.

Listing 11: EventBasedAsync's MainWindow Code-behind

namespace EventBasedAsync
{
    public partial class MainWindow : Window
    {
        private int taskId = 1;

        public MainWindow()
        {
            var token =
                StopwatchHelper.StartNew(TimerType.MAIN_TIMER);
            StopwatchHelper.Stop(token);

            InitializeComponent();
        }

        private void StartSlowProcessButton_Click(object sender,
            RoutedEventArgs e)
        {
        var token = StopwatchHelper.StartNew(
            TimerType.START_CLICK_TIMER);

        // Create new UserControl and add it to the StackPanel
        var uc = new SlowProcessView();
        uc.ViewModel = new SlowProcessViewModel();
        uc.InitializeEvents();

        uc.DataContext = uc.ViewModel;
        MainStackPanel.Children.Add(uc);

        // Get a unique task id, in a thread-safe manner, for
        // task lifecycle management
        var tid = Interlocked.Increment(ref taskId);
        uc.ViewModel.SlowProcessAsync(tid);

        StopwatchHelper.Stop(token);
        }
    }
}

Listing 12: EventBasedAsync's SlowProcessView Code-behind

namespace EventBasedAsync
{
    // Interaction logic for SlowProcessView.xaml
    public partial class SlowProcessView : UserControl
    {
        public SlowProcessView()
        {
            InitializeComponent();
        }

        public void InitializeEvents()   
        {
            // Initialize ViewModel events with View-based event
            // handler methods
            ViewModel.SlowProcessStarted +=
                new SlowProcessStartedEventHandler(
                SlowProcessStartedEventHandlerMethod);
            ViewModel.SlowProcessProgressChanged +=
                new ProgressChangedEventHandler(
                SlowProcessProgressEventHandlerMethod);
        }

        public SlowProcessViewModel ViewModel { get; set; }

        private void SlowProcessStartedEventHandlerMethod(EventArgs e)
        {
            var token = StopwatchHelper.StartNew(
                TimerType.ASYNCOP_POST_TIMER);

            StartedText.Text = "Slow process started.";

            StopwatchHelper.Stop(token);
        }

        public void SlowProcessProgressEventHandlerMethod(
            ProgressChangedEventArgs e)
        {
            var token = StopwatchHelper.StartNew(
                TimerType.ASYNCOP_POST_TIMER);

            ProgressText.Text =
                Convert.ToString(e.ProgressPercentage / 20);
                ProgressBar.Value = e.ProgressPercentage / 20;

            StopwatchHelper.Stop(token);
        }

        private void CancelSlowProcessButton_Click(object sender, 
            RoutedEventArgs e)
        {
            var token = StopwatchHelper.StartNew(
                TimerType.CANCEL_CLICK_TIMER);

            ViewModel.CancelAsync(ViewModel.Tag);

            StopwatchHelper.Stop(token);
        }
    }
}

However, the TextBox bindings are modified for this example, as follows:

<TextBlock x:Name="StartedText" 
           HorizontalAlignment="Center" />
<TextBlock x:Name="ProgressText" 
           FontSize="30"
           FontWeight="Bold"
           HorizontalAlignment="Center" />
<TextBlock x:Name="ResultText"
           Text="{Binding ResultData}"
           HorizontalAlignment="Center" />

Notice that StartedText and ProgressText do not employ a Binding, but ResultText does. The first two TextBlocks are intended to show the way that an application - typically a WinForms application at the time the EAP was first introduced - could use the EAP to provide UI updates from an asynchronous operation not running on the Main/UI thread. The ResultText binding, however, shows the EAP adapted for use with modern MVVM architecture. Note that you are unlikely to come across the MVVM type implementation in legacy code, and it would not likely be a good choice for new code, but it is, nonetheless, instructive to see how the modern MVVM architecture can be implemented with the EAP.

Listing 13: EventBasedAsync's SlowProcessViewModel

namespace EventBasedAsync
{
    // Define delegates for each event we want to publish
    public delegate void SlowProcessStartedEventHandler(
        EventArgs e);
    public delegate void ProgressChangedEventHandler(
        ProgressChangedEventArgs e);
    public delegate void SlowProcessCompletedEventHandler(
        SlowProcessCompletedEventArgs e);

    // Define SlowProcessCompletedEventArgs
    public class SlowProcessCompletedEventArgs : AsyncCompletedEventArgs
    {
        public SlowProcessCompletedEventArgs(Exception e,
            bool canceled, object state) : base(e, canceled, state)
        {
        }

    }

    public class SlowProcessViewModel : INotifyPropertyChanged
    {
        // Delegate on which to invoke async process
        private delegate void SlowProcessWorkerDelegate(
            AsyncOperation asyncOp);

        // Private dictionary to manage async op state lifetime
        private HybridDictionary _userStateToLifetime =
            new HybridDictionary();

        // Delegate instances for posting back to the calling
        // thread from the invoked thread
        private SendOrPostCallback _startedCallbackDelegate;
        private SendOrPostCallback _progressCallbackDelegate;
        private SendOrPostCallback _completedCallbackDelegate;

        // Public events
        public event SlowProcessStartedEventHandler
            SlowProcessStarted;
        public event ProgressChangedEventHandler
            SlowProcessProgressChanged;
        public event SlowProcessCompletedEventHandler
            SlowProcessCompleted;

        public SlowProcessViewModel()
        {
            InitializeDelegates();
            SlowProcessCompleted +=
                new SlowProcessCompletedEventHandler(
                SlowProcessCompletedEventHandlerMethod);
        }

        protected virtual void InitializeDelegates()
        {
            _startedCallbackDelegate = new
                SendOrPostCallback(SlowProcessStartedCallback);
            _progressCallbackDelegate = new
                SendOrPostCallback(SlowProcessProgressCallback);
            _completedCallbackDelegate = new
                SendOrPostCallback(SlowProcessCompletedCallback);
        }

        // Implementation

        public virtual void SlowProcessAsync(object taskId)
        {
            var token = StopwatchHelper.StartNew(
                TimerType.ASYNC_PROCESS_TIMER);

            // Create an AsyncOperation for taskId
            AsyncOperation asyncOp =
                AsyncOperationManager.CreateOperation(taskId);

            // Multiple threads will access the task dictionary,
            // so it must be locked to serialize access.
            lock (_userStateToLifetime.SyncRoot)
            {
                if (_userStateToLifetime.Contains(taskId))
                {
                    throw new ArgumentException("Task ID parameter
                        must be unique", "taskId");
                }

                Tag = taskId;
                _userStateToLifetime[taskId] = asyncOp;
            }

        // Start the asynchronous operation using the delegate
        // BeginInvoke method, passing the asyncOp object
        SlowProcessWorkerDelegate workerDelegate = new
            SlowProcessWorkerDelegate(SlowProcess);
        workerDelegate.BeginInvoke(asyncOp,
            EndSlowProcessCallbackMethod, null);

        StopwatchHelper.Stop(token);
        }

        private void SlowProcess(AsyncOperation asyncOp)
        {
            var token =
                StopwatchHelper.StartNew(TimerType.WORKER_TIMER);

            asyncOp.Post(_startedCallbackDelegate, new EventArgs());

            bool taskCanceled = false;
            for (var i = 0; i <= 5; i++)
            {
                if (TaskCanceled(asyncOp.UserSuppliedState))
                {
                    SlowProcessCompletionMethod(null, true,
                        asyncOp);
                    taskCanceled = true;
                    break;
                }

                Thread.Sleep(TimeSpan.FromSeconds(1));
                var progressState = new
                ProgressChangedEventArgs(
                    i * 20, // put into percentage terms to
                            // satisfy API
                    asyncOp.UserSuppliedState);

                    asyncOp.Post(_progressCallbackDelegate, progressState);
            }

            if (!taskCanceled)
            SlowProcessCompletionMethod(null, false, asyncOp);

            StopwatchHelper.Stop(token);
        }

        private void EndSlowProcessCallbackMethod(IAsyncResult ar)
        {
            var token = StopwatchHelper.StartNew(
                TimerType.ASYNC_ENDINVOKE_TIMER);

            var asyncRes = (AsyncResult)ar;
            SlowProcessWorkerDelegate spd =
                (SlowProcessWorkerDelegate)asyncRes.AsyncDelegate;
            spd.EndInvoke(asyncRes);

            StopwatchHelper.Stop(token);
        }

        public object Tag { get; set; }

        // Started Methods

        private void SlowProcessStartedCallback(object state)
        {
            var e = new EventArgs();

            RaiseSlowProcessStarted(e);
        }

        protected void RaiseSlowProcessStarted(EventArgs e)
        {
            if (SlowProcessStarted != null)
            {
                SlowProcessStarted(e);
            }
        }

        // Progress Methods

        private void SlowProcessProgressCallback(object state)
        {
            ProgressChangedEventArgs e = state as
                ProgressChangedEventArgs;

            RaiseProgressChanged(e);
        }

        protected void RaiseProgressChanged(
            ProgressChangedEventArgs e)
        {
            if (SlowProcessProgressChanged != null)
            {
                SlowProcessProgressChanged(e);
            }
        }

        // Completion and Cancellation Methods

        // Completion methods

        // This is the method that the underlying, free-threaded
        // asynchronous behavior will invoke.  This will
        // happen on an arbitrary thread.
        private void SlowProcessCompletionMethod(
            Exception exception, bool canceled,
            AsyncOperation asyncOp)
        {
            // If the task was not previously canceled,
            // remove the task from the lifetime collection.
            if (!canceled)
            {
                lock (_userStateToLifetime.SyncRoot)
                {
                    _userStateToLifetime.Remove(
                        asyncOp.UserSuppliedState);
                }
            }

            // Package the results of the operation in a
            // SlowProcessCompletedEventArgs.
            var e = new SlowProcessCompletedEventArgs(exception,
                canceled, asyncOp.UserSuppliedState);

            // End the task. The asyncOp object is responsible
            // for marshalling the call to the
            // SynchroniationContext thread.
            asyncOp.PostOperationCompleted(
                _completedCallbackDelegate, e);

            // Note that after the call to PostOperationCompleted,
            // asyncOp is no longer usable, and any attempt to
            // use it will cause an exception to be thrown.
        }

        // This method is invoked via the AsyncOperation object,
        // so it is guaranteed to be executed on the correct
        // thread.
        private void SlowProcessCompletedCallback(
            object operationState)
        {
            var e = operationState as SlowProcessCompletedEventArgs;
            RaiseSlowProcessCompleted(e);
        }

        protected void RaiseSlowProcessCompleted(
            SlowProcessCompletedEventArgs e)
        {
            if (SlowProcessCompleted != null)
            {
                SlowProcessCompleted(e);
            }
        }

        // Cancel methods

        // This method cancels an asynchronous operation by removing
        // the task from lifetime dictionary
        public void CancelAsync(object taskId)
        {
            AsyncOperation asyncOp = _userStateToLifetime[taskId]
                as AsyncOperation;
            if (asyncOp != null)
            {
                lock (_userStateToLifetime.SyncRoot)
                {
                    _userStateToLifetime.Remove(taskId);
                }
            }
        }

        // Utility method for determining if a task
        // has been canceled.
        private bool TaskCanceled(object taskId)
        {
            return (_userStateToLifetime[taskId] == null);
        }

        // Completion and Cancellation Properties

        public void SlowProcessCompletedEventHandlerMethod(
            SlowProcessCompletedEventArgs e)
        {
            var token = StopwatchHelper.StartNew(
                TimerType.UPDATE_RESULT_TIMER);

            if (e.Cancelled)
                ResultData = "Slow process canceled.";
            else
                ResultData = "Slow process completed.";

            StopwatchHelper.Stop(token);
        }

        private string _resultData = "";
        public string ResultData
        {
            get
            {
                return _resultData;
            }
            set
            {
                _resultData = value;
                RaisePropertyChanged("ResultData");
            }
        }

        #region Implement INotifyPropertyChanged

        public void RaisePropertyChanged(string name)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this,
                    new PropertyChangedEventArgs(name));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

    }
}

In order to not clutter the output results with additional output, unlike the earlier examples, you only start one SlowProcess for this example, and then cancel it after three seconds. The user steps are:

  1. Click Start for the first process.
  2. Click Cancel for the first process.

The output results produced look like those shown in Listing 14.

Listing 14: EventBasedAsync's Output Results

 1 Identify Main Thread (Started):  10
 2 Identify Main Thread (Ending):   10 Elapsed: 5
 3 Start Click Thread (Started):    10
 4 Async Process Thread (Started):  10
 5 Async Process Thread (Ending):   10 Elapsed: 3
 6 Start Click Thread (Ending):     10 Elapsed: 19
 7 Worker Thread (Started):          7
 8 AsyncOp Post Thread (Started):   10
 9 AsyncOp Post Thread (Ending):    10 Elapsed: 3
10 AsyncOp Post Thread (Started):   10
11 AsyncOp Post Thread (Ending):    10 Elapsed: 2
12 AsyncOp Post Thread (Started):   10
13 AsyncOp Post Thread (Ending):    10 Elapsed: 2
14 AsyncOp Post Thread (Started):   10
15 AsyncOp Post Thread (Ending):    10 Elapsed: 2
16 Cancel Click Thread (Started):   10
17 Cancel Click Thread (Ending):    10 Elapsed: 3
18 AsyncOp Post Thread (Started):   10
19 AsyncOp Post Thread (Ending):    10 Elapsed: 2
20 Worker Thread (Ending):           7 Elapsed: 4038
21 Update Result Thread (Started):  10
22 Async EndInvoke Thread (Started): 7
23 Async EndInvoke Thread (Ending):  7 Elapsed: 5
24 Update Result Thread (Ending):   10 Elapsed: 7

The UI results are shown in Figure 3.

Figure 3: Event-based Async SlowProcess Application (EAP) running with one canceled process
Figure 3: Event-based Async SlowProcess Application (EAP) running with one canceled process

The key difference between the output results in Listing 14 and the prior examples are the “AsyncOp Post Thread” entries. These entries show where an asynchronous operation posted data back to the original calling synchronization context.

Let's see why this capability is so valuable and how the added ability to pass around the synchronization context further builds upon the groundwork for the modern task-based asynchronous pattern using async and await to provide powerful and automated handling of the synchronization context for us.

A Note about SynchronizationContext

The synchronization context is simply a representation of the type of thread that you would like to be able to access from another thread. In the following code taken from the SlowProcessAsync method in Listing 13, the AsyncOperationManager class captures the current synchronization context and stores it on the SynchonizationContext property of the newly created AsyncOperation object:

// Create an AsyncOperation
AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(taskId);

By capturing the SynchronizationContext, asyncOp allows you to “post” work and data back to the captured SynchronizationContext. In the code, you obtain the DispatcherSynchronizationContext type synchronization context because that is the SynchronizationContext of the Main/UI thread, and asyncOp is created in code that's running on the Main/UI thread. Note, even though asyncOp is created in the SlowProcessAsync method, as shown by Lines 4 and 5 in the output results (Listing 14), the code is still running on the Main/UI thread. Only at the end of that method is the asynchronous operation invoked on a worker thread.

Because asyncOp is an object, it can be passed around freely. Unlike with the Dispatcher.BeginInvoke() method, you aren't dependent upon a UI element to give you access to the Main/UI thread from an asynchronous operation.

How the Event-based Asynchronous Pattern Works

The EAP in the sample XAML MVVM application works by raising events that result in changes in the UI. The key methods are the SlowProcessAsync method and the SlowProcess method in Listing 13.

As discussed above, an object of type AsyncOperation is created in the SlowProcessAsync method. After that's done, you add an entry to the _userStateToLifetime HybridDictionary. This dictionary is used to determine if a particular asynchronous operation is still active, or if it has completed or been canceled. Lastly, the SlowProcessAsync method uses a delegate of type SlowProcessWorkerDelegate to invoke the SlowProcess method, passing the AsyncOperation object and a callback method that will be called automatically when the BeginInvoke method completes. The callback method is where you will call the EndInvoke method on the delegate to signal the delegate's WaitHandle and perform the standard cleanup.

In the SlowProcess method, you can see where to use the Post method on the AsyncOperation class to invoke the _startedCallbackDelegate delegate instance, which, in turn, invokes the SlowProcessStartedCallback method on the synchronization context provided by asyncOp. In this example, that context is the DispatcherSynchronizationContext, which contains just one thread, the Main/UI thread. Although some synchronization contexts contain more than one thread where the continuation thread is not guaranteed, such as the Default (ThreadPool) SynchronizationContext, the DispatcherSynchronizationContext contains just one thread, the Main/UI thread. Posting code back to the DispatcherSynchronizationContext is guaranteed to run on the expected thread, as shown by Lines 8 and 9 in the output results (Listing 14). Similarly, the ProgressText is updated by a Post-back to the Main/UI thread, as seen in Lines 11 to 15 and Lines 18 to 19 of the output results.

However, recall how you left the ResultText to be updated by a data binding? Here, if you look at the call to SlowProcessCompletionMethod following either the completion or cancellation of the SlowProcess, you can see that the PostOperationCompleted method invokes the delegate instance _completedCallbackDelegate. That delegate instance points to the SlowProcessCompletedCallback method, which simply raises the SlowProcessCompleted event.

In the constructor, the SlowProcessCompletedEventHandlerMethod is subscribed to the SlowProcessCompleted event, and it updates the ResultData property, to which the ResultText is data-bound. Because the invocation of delegate instance _completedCallbackDelegate was posted back to the Main/UI thread using the PostOperationCompleted method on asyncOp, the update resulting from raising the event is also run on the Main/UI thread. Therefore, it doesn't result in an InvalidOperationException, and it also doesn't produce a NotSupportedException if it was bound to a collection. The execution thread for this code is confirmed by Lines 21 and 24 in the output results (Listing 14).

Note that after the call to PostOperationCompleted, asyncOp is no longer usable and any attempt to use it causes an exception to be thrown.

Wrapping up the EAP

As shown above, the EAP requires a fair bit of complexity to implement. In addition, the EAP, like the previous patterns, still relies on callback methods, which tend to have the effect of making code-flow paths difficult to follow and difficult to debug. The up side is that consuming events is a fairly familiar exercise for most developers, and so consuming an asynchronous operation implemented using the EAP and handling the results is a fairly straightforward exercise. Also, all of the code for implementing the EAP is encapsulated in the implementing component - the client consuming the asynchronous operation has no responsibility for setting up the asynchronous operation. Lastly, by using the SynchronizationContext property of the AsyncOperation class, access to a synchronization context can be decoupled from its source thread, allowing code on another thread to post code on the originating type of thread.

Challenges of Asynchronous Programming Recap

To recap, as seen in each of the above asynchronous programming patterns, certain challenges are intrinsic to asynchronous programming, despite the pattern employed:

  • A callback method is required for code continuation following the completion of an asynchronous operation; callbacks add significant complexity to code flow and debugging.
  • Thread and context affinity requirements burden the developer with ensuring that they can execute code back on the required thread wherever necessary; in the patterns seen so far, this was accomplished via the Dispatcher available from a UI element, or the object stored on the AsyncOperation.SynchronizationContext property.
  • Asynchronous operation coordination and monitoring is often required, and IAsyncResult provided a mechanism to achieve that.
  • In the patterns explored, managing the above requirements is often complex and onerous for the developer of an asynchronous operation.

Introducing the Task-based Asynchronous Pattern Using Async and Await

Simply put, the Task-based asynchronous pattern (TAP) using async and await have removed all of the complexities present in the earlier asynchronous patterns from the XAML application programmer's concern while retaining all of the powerful features and control. Although it's still possible for the programmer to take complete control of the process by using the Task<TResult> class directly, when using async and await, all of the above-described challenges of asynchronous programming are handled automatically for the programmer by the compiler's treatment of the async and await keywords!

Let's take a look and see how this works.

To show the full power and separation of concerns enabled by using TAP with async and await, this example opts to update all of the UI elements in the SlowProcessView using MVVM and data binding:

<TextBlock x:Name="StartedText"
           Text="{Binding StartData}"
           HorizontalAlignment="Center" />
<TextBlock x:Name="ProgressText"
           Text="{Binding ProgressData}"
           FontSize="30"
           FontWeight="Bold"
           HorizontalAlignment="Center" />
<TextBlock x:Name="ResultText"
           Text="{Binding ResultData}"
        HorizontalAlignment="Center" />

Again, like with the EAP example, you keep the user steps limited to starting just one SlowProcess for three seconds, and then cancel it. The user steps look like this:

  1. Click Start for the first process.
  2. Click Cancel for the first process.

The output results produced look like those shown in Listing 18. The UI results are shown in Figure 4.

Figure 4: A Task-based Async SlowProcess application running with one canceled process
Figure 4: A Task-based Async SlowProcess application running with one canceled process

To begin, as shown in Listing 15, the StartSlowProcessButton_Click click handler method handles the initial start click in MainWindow. As shown on Line 3 of the output results (Listing 18), that code is run on the Main/UI thread. In that method, you build a SlowProcessView object and assign its DataContext to a new SlowProcessViewModel. You then make your first call to the SlowProcessAsync method on the SlowProcessViewModel that you just created.

Listing 15: TaskBasedAsync's MainWindow Code-behind

namespace TaskBasedAsync
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            var token = StopwatchHelper.StartNew(TimerType.MAIN_TIMER);

            InitializeComponent();

            StopwatchHelper.Stop(token);
        }

        private void StartSlowProcessButton_Click(object sender,
            RoutedEventArgs e)
        {
            var token = StopwatchHelper.StartNew(
                TimerType.START_CLICK_TIMER);

            // Create new UserControl and add it to the StackPanel
            var uc = new SlowProcessView();
            uc.ViewModel = new SlowProcessViewModel();
            uc.DataContext = uc.ViewModel;
            MainStackPanel.Children.Add(uc);

            // Call our asynchronous operation
                Task<string> task = uc.ViewModel.SlowProcessAsync();

            StopwatchHelper.Stop(token);

            // Artificial wait to demonstrate monitoring and
            // coordination capability of TAP
            var waitToken =
                StopwatchHelper.StartNew(TimerType.WAIT_TIMER);
            task.Wait(TimeSpan.FromSeconds(1));
            StopwatchHelper.Stop(waitToken);
        }
    }
}

Listing 16: TaskBasedAsync's SlowProcessView Code-behind

namespace TaskBasedAsync
{
    // Interaction logic for SlowProcessView.xaml
    public partial class SlowProcessView : UserControl
    {
        public SlowProcessView()
        {
            InitializeComponent();
        }

        public SlowProcessViewModel ViewModel { get; set; }

        private void CancelSlowProcessButton_Click(object sender,
            RoutedEventArgs e)
        {
            var token = StopwatchHelper.StartNew(
                TimerType.CANCEL_CLICK_TIMER);

            ViewModel.CancellationTokenSource.Cancel();

            StopwatchHelper.Stop(token);
        }
    }
}

The SlowProcessAsync method (Listing 17) starting to run is shown by Line 4 of the output results in Listing 18.

Listing 17: TaskBasedAsync's SlowProcessViewModel

namespace TaskBasedAsync
{
    public class SlowProcessViewModel : INotifyPropertyChanged
    {
        public async Task<string> SlowProcessAsync()
        {
            // Synchronous code

            var token = StopwatchHelper.StartNew(
                TimerType.ASYNC_PROCESS_TIMER);

            // Get a CancellationToken
            var tokenSource = new CancellationTokenSource();
            var cancelToken = tokenSource.Token;
            CancellationTokenSource = tokenSource;

            string resultPromise = await Task.Run<string>(
                () => SlowProcess(cancelToken), cancelToken);

            // Continuation code

            // Continuation code runs on a thread from the
            // synchronization context captured by await

            ResultData = resultPromise;

            StopwatchHelper.Stop(token);

            return resultPromise;
        }

        public string SlowProcess(CancellationToken cancelToken)
        {
            var swToken =
                StopwatchHelper.StartNew(TimerType.WORKER_TIMER);

            StartData = "Slow process started.";

            var rv = String.Empty;
            for (var i = 0; i <= 5; i++)
            {
                if (cancelToken.IsCancellationRequested)
                {
                    rv = "Slow process canceled.";
                    break;
                }

                Thread.Sleep(TimeSpan.FromSeconds(1));

                // XAML framework (here, WPF) helps us by
                // marshalling UI update resulting from
                // data-bound variable update to the
                // Main / UI thread
                ProgressData = i;
            }

            if (rv == String.Empty)
            rv = "Slow process completed.";

            StopwatchHelper.Stop(swToken);

            return rv;
        }

        private string _startData = "";
        public string StartData
        {
            get
            {
                return _startData;
            }
            set
            {
                _startData = value;
                RaisePropertyChanged("StartData");
            }
        }

        private int _progressData = 0;
        public int ProgressData
        {
            get
            {
                return _progressData;
            }
            set
            {
                _progressData = value;
                RaisePropertyChanged("ProgressData");
            }
        }

        private string _resultData = "";
        public string ResultData
        {
            get
            {
                return _resultData;
            }
            set
            {
                _resultData = value;
                RaisePropertyChanged("ResultData");
            }
        }

        public CancellationTokenSource CancellationTokenSource
        { get; set; }

        #region Implement INotifyPropertyChanged

        public void RaisePropertyChanged(string name)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this,
                new PropertyChangedEventArgs(name));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion
    }
}

Listing 18: TaskBasedAsync's Output Results

 1 Identify Main Thread (Started):     10
 2 Identify Main Thread (Ending):      10 Elapsed: 38
 3 Start Click Thread (Started):       10
 4 Async Process Thread (Started):     10
 5 Worker Thread (Started):            11
 6 Start Click Thread (Ending):        10 Elapsed: 26
 7 Start Click Thread (Start Waiting): 10
 8 Start Click Thread (End Waiting):   10 Elapsed: 1005
 9 Cancel Click Thread (Started):      10
10 Cancel Click Thread (Ending):       10 Elapsed: 3
11 Worker Thread (Ending):             11 Elapsed: 4009
12 Async Process Thread (Ending):      10 Elapsed: 4025

Look carefully at what happens here. The SlowProcessAsync method is run synchronously on the same thread that called it - here, that's thread 10, the Main/UI thread. Merely calling into a method decorated with the async keyword and returning a Task<TResult> object won't cause the enclosed code to run asynchronously on a new thread. Code will begin running on a new thread only when code execution reaches a line that meets the following criteria:

  • The line is decorated with the await keyword.
  • The line invokes a method asynchronously using Task.Run().

That is to say, only when the running code reaches Task.Run() in the following snippet, is asynchrony introduced into the application; all prior code is executed synchronously:

public async Task<string> SlowProcessAsync()
{
    .
    . // synchronous code
    .
    string resultPromise = await Task.Run<string>(
        () => SlowProcess(cancelToken),
        cancelToken);
    .
    . // continuation code
    .
    return resultPromise;
}

Merely calling into a method decorated with the async keyword and returning a Task<TResult> object won't cause the enclosed code to run asynchronously on a new thread.

The Async Keyword is Necessary but Not Sufficient

An asynchronous method contains at least one awaitable method. The above code snippet from Listing 17 shows the common elements and structure for an asynchronous method in TAP using async and await. As noted above, a method is not asynchronous just because it's marked with the async keyword; it must contain awaitable code that's run asynchronously using the Task.Run() method.

In the above snippet from Listing 17, SlowProcessAsync is named using the Async suffix by convention. The method returns a Task<TResult>. TResult is also referred to as the task “promise,” as it's the result that the asynchronous operation “promises” to return at some point. Note, when you use the async keyword to decorate a method, it's valid for the method signature to indicate a return type of Task<string>, while the method itself ends by returning a result of type string. The reason for this apparent mismatch is described further below.

Await Does a Lot of Work

The above code snippet showed where normal synchronous code flow is interrupted by the line containing the await keyword. Let's unpack that line a bit. As shown, the Task.Run method is called. There are several overloads of the Task.Run method. In this particular example, Task.Run<TResult>(Func<TResult>, CancellationToken) is called. It's at this point that all of the complexities of asynchronous programming described in the previous patterns get handled automatically by the compiler.

Upon reaching the await keyword, control of the current thread is yielded back to the calling method immediately, returning a Task<string> object to that code. This is indicated by Line 6 in the output results (Listing 18), which shows that the Start Click Thread is ending after just 26 milliseconds has elapsed. In this way, a handle is returned to the calling thread immediately, providing a mechanism for control and coordination of the asynchronous operation by the calling method. Of course, no result is available from the asynchronous operation yet, but the calling method now has a handle so that it can be signaled when the asynchronous method completes and has a result available. You may recall that I mentioned in the section covering the DAP that, like AsyncResult, the Task class also implements IAsyncResult, and therefore, offers the same functionality as AsyncResult, and then some.

Using Task.Run(), SlowProcess is invoked asynchronously on a ThreadPool thread. Notice that the Task.Run method is expecting a Func<TResult>–not a Func<string,TResult>. Here, you're able to bring in the cancelToken object of type CancellationToken via a lambda expression closure. This is a common technique for bringing context variables into code that is invoked asynchronously using the Task.Run method. Another option would be to store and retrieve the CancellationToken on the ViewModel.

Lastly, notice that you don't need to pass in a callback continuation method to Task.Run, such as to call an EndInvoke method to cleanup a WaitHandle, or to retrieve the asynchronous operation result. Here, the async and await mechanism tells the compiler to setup the remainder of the method as continuation code. A major advantage is gained by structuring the code this way; the code reads just like synchronous code, and doesn't invert the code flow by introducing a jump into a callback method. The return value of the asynchronous operation, also called the “promised result,” is returned as shown and is available for use in additional processing in the continuation code.

Moreover, as you can see from Line 11 of the output results (Listing 18), the continuation code of the Async Process Thread has been scheduled and run back on the synchronization context on which you started, namely, the DispatcherSynchronizationContext. In addition to the above work, await captured the starting synchronization context for you automatically as well! In this particular example, continuation code execution is guaranteed to be scheduled on the exact same thread on which the method started because the DispatcherSynchronizationContext only contains a single thread, namely, thread 10, the Main/UI thread. Look at what has been accomplished by async and await - the application programmer doesn't need to learn anything new or do any extra work to manage thread affinity in an asynchronous application!

Look at what has been accomplished by async and await - the application programmer doesn't need to learn anything new or do any extra work to manage thread affinity in an asynchronous application!

A Note about the CancellationToken

As you can see in the implementation of SlowProcess in Listing 17, I opted to use a CancellationToken instead of simply checking an IsCanceled flag, like I did in the Direct Thread and DAP example code. The truth is, I could have used the IsCanceled flag here as well. However, CancellationToken is the official mechanism for handling cancellations for TAP asynchronous operations and this provided a good opportunity to illustrate this additional capability. Also, CancellationToken does provide several useful capabilities that a simple flag cannot offer alone. Among other things, it can be configured to issue an automatic cancellation after a prescribed period of time. You should be aware of this mechanism and favor using it whenever you seek to enable a cancellation feature in a TAP asynchronous operation.

Final Remarks

I have now covered the full evolution of asynchronous programming on the .NET platform as it has unfolded over the past 12 years, using a single simple application. By understanding the features and benefits added by each asynchronous pattern, perhaps you will more fully understand and appreciate the modern task-based asynchronous pattern using async and await, and be in a position to better understand and revisit legacy code, as it may make sense for your application.

Table 1: Asynchronous Pattern Summary

Asynchronous PatternCurrent Status
Direct use of the ThreadPool (.NET 1.0)Legacy, not recommended for new code
Delegate-base Asynchronous Pattern (DAP) using IAsyncResult (.NET 1.0)Legacy, not recommended for new code
Event-based Asynchronous Pattern (EAP) (.NET 2.0)Legacy, not recommended for new code
Task-based Asynchronous Pattern (TAP) (.NET 4.0) using async and await (.NET 4.5)Current, recommended for new code - either using or not using async and await

Back to article