Jul 1, 2012

Multithreading - Asynchronous Method Cancellation

Leave a Comment
This example explains how to implement support for asynchronous method cancellation in your class. It is based on the Create an Asynchronous Method example.

We will implement a CancelAsync method that signals the asynchronous worker to cancel the operation. This is done by setting a boolean variable that is periodically checked by the asynchronous worker. When the asynchronous worker receives the cancel signal, it finishes and fires the MyTaskCompleted event with the Cancelled flag set.

MyAsyncContext class

To signal the worker, we need an object with a boolean property. This property will be set by the CancelAsync method and periodically checked by the asynchronous worker.

Code:
internal class MyAsyncContext
{
  private readonly object _sync = new object();
  private bool _isCancelling = false;

  public bool IsCancelling
  {
    get
    {
      lock (_sync) { return _isCancelling; }
    }
  }

  public void Cancel()
  {
    lock (_sync) { _isCancelling = true; }
  }
}

We have to add a member of the MyAsyncContext type to our class.
Code:
private MyAsyncContext _myTaskContext = null;

This member is set in the MyTaskAsync method when the asynchronous operation is invoked. The same MyAsyncContext instance is passed also to the asynchronous worker.
Code:
private readonly object _sync = new object();

public void MyTaskAsync(string[] files)
{
  MyTaskWorkerDelegate worker = new MyTaskWorkerDelegate(MyTaskWorker);
  AsyncCallback completedCallback = new AsyncCallback(MyTaskCompletedCallback);

  lock (_sync)
  {
    if (_myTaskIsRunning)
      throw new InvalidOperationException("The control is currently busy.");

    AsyncOperation async = AsyncOperationManager.CreateOperation(null);
    MyAsyncContext context = new MyAsyncContext();
    bool cancelled;

    worker.BeginInvoke(files, context, out cancelled, completedCallback, async);

    _myTaskIsRunning = true;
    _myTaskContext = context;
  }
}

Asynchronous code modifications

The asynchronous worker must be modified to support cancellation. As you could notice in MyTaskAsync code, we added one input parameter of type MyAsyncContext and one output boolean parameter. The latter one indicates, whether the operation has been cancelled, when the worker finishes.

Code:

private delegate void MyTaskWorkerDelegate(string[] files,
  MyAsyncContext asyncContext, out bool cancelled);
 
private void MyTaskWorker(string[] files, MyAsyncContext asyncContext,
  out bool cancelled)
{
  cancelled = false;

  foreach (string file in files)
  {
    // a time consuming operation with a file (compression, encryption etc.)
    Thread.Sleep(1000);

    if (asyncContext.IsCancelling)
    {
      cancelled = true;
      return;
    }
  }
}

The worker periodically test the asyncContext.Is­Cancelling property. If the property is set to true, the output parameter cancelled is set and the method is finished.
The value of the cancelled parameter is copied to the AsyncComplete­dEventArgs.Can­celled property in asynchronous operation completed callback.

private void MyTaskCompletedCallback(IAsyncResult ar)
{
  // get the original worker delegate and the AsyncOperation instance
  MyTaskWorkerDelegate worker =
      (MyTaskWorkerDelegate)((AsyncResult)ar).AsyncDelegate;
  AsyncOperation async = (AsyncOperation)ar.AsyncState;
  bool cancelled;

  // finish the asynchronous operation
  worker.EndInvoke(out cancelled, ar);

  // clear the running task flag
  lock (_sync)
  {
    _myTaskIsRunning = false;
    _myTaskContext = null;
  }

  // raise the completed event
  AsyncCompletedEventArgs completedArgs = new AsyncCompletedEventArgs(null,
    cancelled, null);
  async.PostOperationCompleted(
    delegate(object e) { OnMyTaskCompleted((AsyncCompletedEventArgs)e); },
    completedArgs);
}

CancelAsync method

This method signals the asynchronous worker to cancel the operation. It uses the MyAsyncContext instance that was created during asynchronous operation invokation.

Code:
public void CancelAsync()
{
  lock (_sync)
  {
    if (_myTaskContext != null)
      _myTaskContext.Cancel();
  }
}

Note: If you have only one asynchronous method in your class, consider renaming the CancelAsync method to a name similar to MyTaskAsyncCan­cel. All the asynchronous operation related methods will then be displayed together in Visual Studio.


If you feel this is helpful or you like it, Please share this using share buttons available on page.

0 comments :

Post a Comment