tabs ↹ over ␣ ␣ ␣ spaces

by Jiří {x2} Činčura

Wrapping event-based asynchronous pattern (EAP) into task-based asynchronous pattern (TAP)

21 May 2014 .NET, C#, Multithreading/Parallelism/Asynchronous/Concurrency

After my presentation this week on WUG I got an interesting question. How to wrap event-based asynchronous pattern (EAP) into task-based asynchronous pattern (TAP). Obviously my mind immediately picked up the TaskCompletetionSource<T> class as a viable solution.

And indeed it’s not difficult. The original question used ReverseGeocodeQuery class, but because I don’t have Windows Phone SDK etc. installed I went to good old SmtpClient (just to make sure, SmtpClient does support TAP from .NET 4.5). 😃 I first tried to write some generic wrapper that would work no matter what class you’re using, but it turned out to be kind of not nice method to call - at least for me. Too much clutter.

Anyway here’s now to wrap SmtpClient’s EAP into TAP.

using (var client = new SmtpClient())
{
	var tcs = new TaskCompletionSource<object>();
	var handler = default(SendCompletedEventHandler);
	handler = new SendCompletedEventHandler((sender, eventArgs) =>
	{
		var tcsLocal = (TaskCompletionSource<object>)eventArgs.UserState;
		try
		{
			if (eventArgs.Error != null)
			{
				tcsLocal.SetException(eventArgs.Error);
				return;
			}
			if (eventArgs.Cancelled)
			{
				tcsLocal.SetCanceled();
				return;
			}
			// set result, if any
			tcsLocal.SetResult(null);
			return;
		}
		finally
		{
			client.SendCompleted -= handler;
		}
	});
	client.SendCompleted += handler;
	client.SendAsync(new MailMessage("foo@example.com", "bar@example.com", "Subject", "Body"), tcs);
	await tcs.Task;
	// ...
}

I’ll walk through the code. First I create the TaskCompletionSource<object> (object because SmtpClient does not return any value and object minimal “object” to put something into the generic parameter) and then I create handler to "finish" the operation. If there’s and error, I’ll call SetException with appropriate exception from the property and I’m done. If not, I’ll check whether the operation was cancelled and if so I’ll call SetCanceled. If there was no error nor cancellation I’m ready to SetResult (in this case I simply set null, because I don’t have anything better). And that’s it. You’re done.

As I said above I initially thought about creating some wrapper. I wrote one, but I don’t like how it turned out. I feel kind of there’s too much noise - generic parameters, factories to create some type (because there’s not a common base type); and also calling isn’t looking nice. Below is the code anyway. 😎 Maybe some reader will find a nicer way to wrap it into C# language.

static Task WrapEapToTap<TObject, TEventHandler, TEventArgs, TResult>(TObject obj,
	Func<TEventArgs, TResult> resultSelector,
	Action<TObject, object> eapAction,
	Action<TObject, TEventHandler> addEventHandler,
	Action<TObject, TEventHandler> removeEventHandler,
	Func<Action<object, TEventArgs>, TEventHandler> eventHandlerFactory)
	where TEventArgs : AsyncCompletedEventArgs
{
	var tcs = new TaskCompletionSource<object>();
	var handler = default(TEventHandler);
	handler = eventHandlerFactory((sender, eventArgs) =>
	{
		var tcsLocal = (TaskCompletionSource<object>)eventArgs.UserState;
		try
		{
			if (eventArgs.Error != null)
			{
				tcsLocal.SetException(eventArgs.Error);
				return;
			}
			if (eventArgs.Cancelled)
			{
				tcsLocal.SetCanceled();
				return;
			}
			tcsLocal.SetResult(resultSelector(eventArgs));
			return;
		}
		finally
		{
			removeEventHandler(obj, handler);
		}
	});
	addEventHandler(obj, handler);
	eapAction(obj, tcs);
	return tcs.Task;
}

If you’d like to use this method to wrap the SmtpClient example it looks like this.

using (var client = new SmtpClient())
{
	await WrapEapToTap<SmtpClient, SendCompletedEventHandler, AsyncCompletedEventArgs, object>(
		client,
		eventArgs => null,
		(c, o) => client.SendAsync(new MailMessage("foo@example.com", "bar@example.com", "Subject", "Body"), o),
		(c, handler) => client.SendCompleted += handler,
		(c, handler) => client.SendCompleted -= handler,
		handler => new SendCompletedEventHandler(handler));
	// ...
}

Anyway I believe you should first try wrapping asynchronous programming model (APM) (that’s the one with BeginXxx and EndXxx methods) using FromAsync method. This model is kind of less hacky and closer to the metal. And any good component should have first and foremost APM and maybe EAP.

If you’re stuck with EAP, I hope the code above helps.