Events Part 2 - Asynchronously call handlers

Events

In the previous post we saw how to make use of events to extend functionality. We also saw that the execution of the handlers were blocking in nature. The execution was synchronous. There might be a real benefit if the handlers were executed asynchronously without blocking the main thread. Please note that I stressed on might. You have to choose carefully if your operations requires parallelism, because this will complicate your application and would take more resources without any benefit in return. If you foresee many handlers listening to an event, this might give you the desired throughput by offloading them to background threads. If you are making the call from a UI thread, it would also be beneficial to free up the UI thread.
Since the events are handled by delegates we can leverage asynchronous methods calls. You can find more information of calling methods asynchronously here as of this writing.

There are a few things we have to consider and be cautious while dealing with events asynchronously. Events return void by default. When you use the generic delegates (EventHandler / EventHandler<EventArgs>) they always return void. If you use custom delegates which return value with your events, it can become awkward to deal with them when they have multiple handlers attached to them. I am not saying it is not possible, just difficult. You should be able to correlate each handlers return value, if not you would end up only with the value from the last handler’s result. Then again if you require return value from your events, then you might nned to use something other than events.

The only thing the subject needs to wait for is until all the handlers complete executing. If the thread (main or foreground) from which the event handlers were invoked exits before the handlers return, which would be running on the background thread, then they would be abruptly terminated. You would also only know if there were any exceptions in any of the handlers when you call the EndInvoke of the delegate.

Asynchronous Programming Pattern (APM) is now obsolete in favour of Task-based Asynchronous Pattern (TAP) and so is Event-Based Asynchronous Pattern (EAP). Calling of Begin method, dealing with IAsynResult and calling of End was not easy to implement and for a client consuming it, the very least, cumbersome.

We can now use Task.Factory.FromAsync which makes it easy to deal with APM calls by returning a Task. One of its overloads takes the Begin/End pair and while taking any additional arguments that gets passed to these methods. If you find the overloads lacking, then you can provide one of your own wrapper using TaskCompletionSource internally similar to FromAsync.

For those projects which cannot yet take advantage of TAP, I recommend using callback method when the call completes as described in MSDN. Through this one can handle multiple handlers since the callback would provide you with a unique IAsyncResult so that you don’t need to keep track of it. It provides the most flexibility and least blocking.

Ok, heading back to implement our asynchronous event handlers, we’ll see that there are not much changes that need to be done to the synchronous implementation. We would be receiving a Task for each Add. Add method signature would now be look as below

1
public async Task AddAsync(string name, int quantity)

Notice we are returning a Task and also marked it as async. Also changed the method name to reflect this as per the conventions.
One for all, all for one. If one method is async in a chain of calls, then it is best to have all the methods up the order as async-await.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class ProductAsyncTask
{
public event EventHandler<ProductAsyncTaskEventArgs> Saved;

// simulate data store
private readonly IDictionary<string, ProductAsyncTask> _productRepository;

public ProductAsyncTask()
{
_productRepository = new Dictionary<string, ProductAsyncTask>();
}

public string Name { get; set; }
public int Quantity { get; set; }

public async Task AddAsync(string name, int quantity)
{
if (_productRepository.ContainsKey(name))
{
_productRepository[name].Quantity = quantity;
Console.WriteLine("Thread# {2}: {0} quantity updated to {1}",
name, quantity, Thread.CurrentThread.ManagedThreadId);
// return a completed task
await Task.CompletedTask;
return;
}
var p = new ProductAsyncTask() { Name = name, Quantity = quantity };
_productRepository.Add(name, p);
Console.WriteLine("Thread# {1}: New Product added - {0} ",
name, Thread.CurrentThread.ManagedThreadId);
// wait without blocking the thread
await OnSaved(p);
Console.WriteLine("Thread# {1}: Completed add for {0}",
name, Thread.CurrentThread.ManagedThreadId);
}

protected async virtual Task OnSaved(ProductAsyncTask p)
{
var multiDels = Saved as EventHandler<ProductAsyncTaskEventArgs>;
if (multiDels == null)
{
await Task.CompletedTask;
return;
}
// list of event subscribers
Delegate[] delList = multiDels.GetInvocationList();
Task[] tasks = new Task[delList.Length];
for (int i = 0; i < delList.Length; i++)
{
EventHandler<ProductAsyncTaskEventArgs> e =
delList[i] as EventHandler<ProductAsyncTaskEventArgs>;
Task t = Task.Factory.FromAsync(
e.BeginInvoke, e.EndInvoke, this,
new ProductAsyncTaskEventArgs(p), null);
tasks[i] = t;
}
await Task.WhenAll(tasks);
}
}

Nothing much has changed inside the AddSync method. We are now awaiting on task or returning a completed task if there is nothing to continue for.
Inside OnSaved we see a little bit more action. We are now getting a list of delegates from the MulticastDelegate and use Task.Factory.FromAsync to create a Task for async call of each delegate. If we would have created a Task using Task.Factory.StartNew or Task.Run and made an Invoke of each delegate, the final outcome would be the same. But, this would have blocked each thread on the thread pool until completion since the task would run synchronously. In FromSync, the EndInvoke call would signal the completion and the thread would not be blocked.

The client application

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var productTask = new ProductAsyncTask();
productTask.Saved += handler1.SendMail;
productTask.Saved += handler2.DoCall;
productTask.Saved += handler3.Audit;

var t1 = productTask.AddAsync("Cadbury classic", 10);
var t2 = productTask.AddAsync("Lindt Dark 80%", 10);
var t3 = productTask.AddAsync("Mars", 10);
var t4 = productTask.AddAsync("Cadbury classic", 10);
Console.WriteLine("Doing some other work here...");
try
{
Task.WaitAll(t1, t2, t3, t4);
}
catch (AggregateException ae)
{
foreach (var e in ae.InnerExceptions)
{
Console.WriteLine(e.Message);
}
}
// unregister
productTask.Saved -= handler1.SendMail;
productTask.Saved -= handler2.DoCall;
productTask.Saved -= handler3.Audit;

I have not including any error handling, but since we have a Task returned, we can follow its exception handling pattern. You can have a continuation task on fault or catch the exceptions closest to its source. Also if the subject is long lived, remember to un-register the handlers.

So we have seen how we can relatively easily make our event handlers run asynchronously using TAP.