Previous month:
April 2010
Next month:
August 2010

July 2010

Cancellation with .NET 4 (Part 2)

Part 1 of this series introduced the async patterns and introduced cancellation with the BackgroundWorker class as it existed since .NET 2. This part shows how the new .NET 4 cancellation framework can be used.

Previously to .NET 4, cancellation with async method calls was implemented in different ways if it was supported at all. For example, 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. More about this in the previous blog entry.

.NET 4 now supports cooperative cancellation of async methods in a standard way. The heart of this new framework is the CancellationToken struct. The CancellationToken is created by a CancellationTokenSource. This token can then be passed to any activity that should be cancelled. In case of a cancellation, the async call can verify cancellation by checking the IsCancellationRequested property of the CancellationToken. This unified model is now available with several classes offering asynchronous requests.

Cancellation of a Parallel Loop

The first example demonstrates cancelling a parallel for loop. Parallel.For is new with .NET 4 for doing iterations in parallel. In the sample the loop is cancelled from a separate task after 500 ms.

First a CancellationTokenSource is created. The separate task to cancel the loop invokes the Cancel method of the CancellationTokenSource to cancel the task(s) that have the cancellation token associated.

The Parallel.For method does a parallel loop from 0 to 100 and invokes the method that is defined by the fourth parameter that is of type Action<int>. The parameter that is received with the Action<int> delegate is the index of the loop. The implementation of this method just loops with a normal for and does a small sleep. At the begin and the end of the implementation a start and finish message is written so it can be seen if the iteration was completed.

With the third parameter ParallelOptions are passed where the CancellationToken property is assigned to the CancellationToken from the CancellationTokenSource. This makes it possible to cancel the loop.

In case of a cancellation, an exception of type OperationCanceledException is thrown.

           var cts = new CancellationTokenSource();

            // start a task that sends a cancel after 500 ms       
            new Task(() =>
               {
                   Thread.Sleep(500);
                   cts.Cancel(false);
               }).Start();

            try
            {
                ParallelLoopResult result =
                   Parallel.For(0, 100,
                       new ParallelOptions
                       {
                           CancellationToken = cts.Token
                       },
                       x =>
                       {
                           Console.WriteLine("loop {0} started", x);
                           int sum = 0;
                           for (int i = 0; i < 100; i++)
                           {
                               Thread.Sleep(2);
                               sum += i;
                           }
                           Console.WriteLine("loop {0} finished", x);
                       });
            }
            catch (OperationCanceledException ex)
            {
                Console.WriteLine(ex.Message);
            }

     

With the parallel for when a cancellation occurs all iterations hat have been started can continue to the end. However, new operations are not started anymore. This can easily be verified by checking the output as shown here.

loop 0 started
loop 50 started
loop 50 finished
loop 51 started
loop 0 finished
loop 1 started
loop 1 finished
loop 2 started
loop 51 finished
loop 52 started
loop 2 finished
loop 52 finished
The operation was canceled.

Cancellation of a Task

The parallel loop can do the cooperative cancellation on its own. This is not possible with an asynchronous running task as there’s no way to know when the task can be cancelled without knowing the method that is called. The implementation of the task method needs to cancel itself in case a cancellation is requested.

Similar as in the previous sample again a CancellationTokenSource is created. The CancellationToken that is associated with the CancellationTokenSource is passed on creation of the TaskFactory. Alternatively a CancellationToken can also be passed on creating a new task. The task that is created from the TaskFactory has an implementation that verifies if cancel should be done by checking the IsCancellationRequested property from the CancellationToken. In case cancel should be done an exception of type TaskCanceledException is thrown with the help of the method ThrowIfCancellationRequested. The type TaskCanceledException derives from the base class OperationCanceledException. The exception can be caught by someone waiting on the task.

            var cts = new CancellationTokenSource();

            // start a task that sends a cancel after 500 ms 
            new Task(() =>
            {
                Thread.Sleep(500);
                cts.Cancel();
            }).Start();

            var factory = new TaskFactory(cts.Token);
            Task t1 = factory.StartNew(new Action<object>(f =>
                {
                    Console.WriteLine("in task");
                    for (int i = 0; i < 20; i++)
                    {
                        Thread.Sleep(100);
                        CancellationToken ct = (f as TaskFactory).CancellationToken;
                        if (ct.IsCancellationRequested)
                        {
                            Console.WriteLine("cancelling was requested");
                            ct.ThrowIfCancellationRequested();
                            break;
                        }
                        Console.WriteLine("in loop {0}", i);
                    }
                    Console.WriteLine("task finished");
                }), factory);

            try
            {
                t1.Wait();
            }
            catch (AggregateException ex)
            {
                foreach (var innerEx in ex.InnerExceptions)
                {
                    Console.WriteLine(innerEx.Message);
                }
            }

It’s always the same. No matter what asynchronous operations are available, they should be cancelable with the help of a CancellationToken. That’s one of the great new features of .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


Cancellation with .NET 4 (Part 1)

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


Resource Management in Interop Scenarios

In a previous blog post I’ve shown a scenario for interop between MFC/C++ and WPF. This blog post didn’t further mention the requirements to release the resources. Naveen also mentioned this in a comment. This information is added here.

With the sample code a UserControl1 and a HwndSource object are instantiated.

HWND CMFCDialogAppDlg::GetUserControl1Hwnd(HWND parent, int x, int y, int width, int height)
{
HwndSourceParameters^ sourceParams = gcnew HwndSourceParameters("MFCWPFApp");
sourceParams->PositionX = x;
sourceParams->PositionY = y;
sourceParams->Height = height;
sourceParams->Width = width;
sourceParams->ParentWindow = IntPtr(parent);
sourceParams->WindowStyle = WS_VISIBLE | WS_CHILD;
m_hwndSource = gcnew HwndSource(*sourceParams);
m_wpfUC = gcnew UserControl1();
m_hwndSource->RootVisual = m_wpfUC;
return (HWND) m_hwndSource->Handle.ToPointer();
}

Both of these types implement the IDisposable interface, and thus the Dispose method should be invoked. Using C++/CLI, the Dispose method can be invoked by using the delete operator. The compiler changes delete to an invocation of the Dispose method.

CMFCDialogAppdlg::~CMFCDialogAppdlg()
{
delete m_hwndSource;
delete m_wpfUC;
}

The gcnew operator allocates an object on the managed heap. The delete operator does not release the object from the managed heap but invokes the Dispose method if available. This is a different behavior from the new/delete operators and the native heap.

What could be changed in the sample in regard to resource consumption is the use of the HwndSourceParameters. HwndSourceParameters is a managed value type and thus can be stored on the managed heap, the native heap, or the stack. Without the gcnew operator sourceParams is on the stack:

HWND CMFCDialogAppDlg::GetUserControl1Hwnd(HWND parent, int x, int y, int width, int height)
{
HwndSourceParameters sourceParams("MFCWPFApp");
sourceParams.PositionX = x;
sourceParams.PositionY = y;
sourceParams.Height = height;
sourceParams.Width = width;
sourceParams.ParentWindow = IntPtr(parent);
sourceParams.WindowStyle = WS_VISIBLE | WS_CHILD;
m_hwndSource = gcnew HwndSource(sourceParams);
m_wpfUC = gcnew UserControl1();
m_hwndSource->RootVisual = m_wpfUC;
return (HWND) m_hwndSource->Handle.ToPointer();
}

Christian

More information: