Feeds:
Posts
Comments

Posts Tagged ‘async’

Async/await is arguably one of the most important language improvement to ever come to C#. But there are a few notable gaps that can leave developers scratching their heads. Chiefly among these is the inability to support async events and properties. Fortunately, there are some patterns that developers can employ to solve these problems. In this article I will demonstrate one such way to support async events.

The following pattern is actually based on Microsoft’s own WinRT deferrable APIs so if you’ve ever built a Windows Phone or Windows Store app, chances are you are already familiar with the consumer side of the pattern.

How it works at a high level

Event handlers merely need to request a deferral from the event argument, run async code, then tell the deferral when they are done (either by calling a .Complete() on the deferral or disposing it). Easy breezy.

Event raisers need to instantiate a DeferrableEventArgs object, raise the event passing along this object to the event handler and then wait until all deferrals requested by the event handlers complete.

How to build and consume

Let’s take a look how to accomplish this in our own custom events.

Event handlers: We will start by examining what the event handler will look like. As mentioned, this should already be familiar to Windows developers if you’ve ever handled a deferrable event such as the Windows.UI.Xaml.Application.Suspending event.

MyClass myClass = new MyClass();
myClass.DoingSomething += MyClass_DoingSomething;
async void MyClass_DoingSomething(object sender, DeferrableEventArgs e)
{
    using (var deferral = e.GetDeferral())
    {
        await Task.Delay(200); // run an async operation
    }            
}

Note: we could also call deferral.Complete() instead of .Dispose (by way of the using statement). Both have the same effect of signaling when the deferral has completed.

Event raisers: Next, let’s look at the code responsible for raising the event and awaiting all event handlers to complete work.

class MyClass
{
 public event EventHandler<DeferrableEventArgs> DoingSomething;

 public async Task DoSomethingAsync()
 {
 if (DoingSomething != null)
 {
 var args = new DeferrableEventArgs();
 DoingSomething(this, args);
 await args.DeferAsync(); // completes once all deferrals are completed.
 }
 }
}

The real magic: The real work happens in the DeferrableEventArgs object (which we need to create). I had hoped to find an existing .NET or WinRT base class already available but sadly, one does not exist. That said, Windows.Foundation.Deferral is available to reuse as the actual deferral object.

public sealed class DeferrableEventArgs
{
 readonly List<TaskCompletionSource<object>> taskCompletionSources;

 public DeferrableEventArgs()
 {
 taskCompletionSources = new List<TaskCompletionSource<object>>();
 }

 public Deferral GetDeferral()
 {
 var tcs = new TaskCompletionSource<object>();
 var deferral = new Deferral(() => tcs.SetResult(null));
 taskCompletionSources.Add(tcs);
 return deferral;
 }

 public IAsyncAction DeferAsync()
 {
 return Task.WhenAll(taskCompletionSources.Select(tcs => tcs.Task)).AsAsyncAction();
 }
}

Let’s dissect… Here we create and maintains a collection of TaskCompletionSource objects. Each time an event handler requests a deferral (by calling .GetDeferral), we create one TaskCompletionSource and mark it as complete (by calling .SetResult) when its associated deferral is signaled as completed by the event handler. The code responsible for raising the event calls DeferAsync to get a task that it can await. Here we use the Task.WhenAll method to combine all TaskCompletionSource tasks (ultimately converted to IAsyncAction to make WinRT compatible before returning it).

Hopefully this pattern will help you fully take advantage of the async await features in your app without compromising the architecture. There are other improvements that could be made here such as allowing event handlers to signal failures and cancellation, supporting deadlines, and sheltering event handlers from the DeferAsync method but hopefully this is ample to get you started with a nice reusable and robust pattern for supporting async events.

Advertisement

Read Full Post »