For calling methods asynchronously, since .NET 1.0 the async pattern can be used. .NET 2.0 added the event-based async pattern (also known as async component pattern) that makes async calls easier with Windows applications. Before .NET 4 no standard mechanism has been available to cancel asynchronous method calls. That’s new with .NET 4.0.
In this blog series you can read about these async patterns as well as the new unified model for cancellation.
Async Pattern
Calling methods asynchronously several classes from the .NET framework implement the async pattern. This pattern is available with file I/O, stream I/O, socket I/O, WCF proxies, Message queuing… and delegates.
If there’s a synchronous Foo method, the async pattern defines the BeginFoo and EndFoo methods. The BeginFoo method returns IAsyncResult that can be used to check the async method if it is completed, and allows waiting for it by using a WaitHandle. Passing a AsyncCallback delegate allows defining a handler method that gets invoked when the async call is finished. The issue here is that the handler method is invoked from a pooled thread and thus if the handler should do some changes on Windows elements a thread switch must be done to the thread owning the Window handles.
Event-based Async Pattern
The async component pattern made async calls with Windows applications easier because the switch to the Window thread is already done.
If there’s a synchronous Foo method, the async component pattern defines the method FooAsync that receives the input paramters of Foo. A event named FooCompleted is invoked when the async method is finished. The advantage of the event model here is that the handler is invoked from a thread that holds the synchronization context. With WPF and Windows Forms application the UI thread is assigned the synchronization context with WindowsFormsSynchronizationContext or DispatcherSynchronizationContext.
The event-based async pattern is implemented by several .NET classes, e.g. the BackgroundWorker, WebClient and WCF proxies.
Cancellation
Async calls can take a while before the result is returned. Thus it can be useful to cancel the call. Before .NET 4 there was no uniform way to support cancellation.
Of course cancellation is available with async classes before .NET 4. For example, the BackgroundWorker supports cancellation as shown in the following code sample.
To enable cancellation with the BackgroundWorker the property WorkerSupportsCancellation must be set to true.
private BackgroundWorker worker; public MainWindow() { InitializeComponent(); worker = new BackgroundWorker(); worker.WorkerSupportsCancellation = true; }
The BackgroundWorker implements the async event pattern by defining the method RunWorkerAsync and the event RunWorkerCompleted. RunWorkerAsync starts the asynchronous call that is defined by the DoWork event. RunWorkerAsync requires single argument of type object. The sample code passes two values from textboxes by creating a tuple.
The DoWork event of the BackgroundWorker defines the method that is invoked when the async request is started. The implementation simulates a long-running method by using a for-loop and Thread.Sleep. Within the loop it is verified if the call should be canceled by checking the CancellationPending property from the BckgroundWorker. If this is the case, the method returns after setting the Cancel property of the DoWorkEventArgs type to mark cancelation.
The RunWorkerCompleted event is fired as soon as the async method is finished, no matter if it was running successful or is canceled. In case of a cancelation an event of type InvalidOperationException is fired with the information “Operation has been cancelled”.
private void button1_Click(object sender, RoutedEventArgs e) { worker.DoWork += (sender1, e1) => { var input = (e1.Argument as Tuple<int, int>); for (int i = 0; i < 10; i++) { Thread.Sleep(1000); if ((sender1 as BackgroundWorker).CancellationPending) { e1.Cancel = true; return; } } e1.Result = input.Item1 + input.Item2; };
worker.RunWorkerCompleted += (sender1, e1) => { try { textBox3.Text = e1.Result.ToString(); } catch (InvalidOperationException ex) { textBox3.Text = ex.Message; } };
worker.RunWorkerAsync(Tuple.Create(int.Parse(textBox1.Text), int.Parse(textBox2.Text))); }
The BackgroundWorker is canceled by invoking the method CancelAsync.
worker.CancelAsync();
In summary, the BackgroundWorker implements cooperative cancellation by invoking the CancelAsync method, the long-running method needs to verify if it should be canceled by checking the CancellationPending property and needs to cancel by setting the Cancel property of the DoWorkEventArgs.
My next blog entry demonstrates how cancellation can be done with .NET 4.
More about asynchronous programming with .NET 4 in my book Professional C# 4 with .NET 4 and in my .NET 4 workshop.
Christian
Comments