Blog

Turn events into Tasks

17 Aug 2016

Sometimes you come accross callback based APIs and that doesn’t fit very well with your nice Task based modern APIs.

Task reminder

Let’s start by reviewing the Task class that represents an asynchronous operation in .NET modern environnement.

A task can have those status :

Manual custom tasks

Thanks to the TaskCompletionSource<T> class, nothing too complex to create task object, and trigger its output status manually when needed.

You must first create a source instance, that has a Task property that can be awaited :

var source = new TaskCompletionSource<string>();
var task = source.Task;

// ...

await task;

Then you can choose the task output status at any time :

// Success
source.SetResult("Success!);

// Cancelled
source.SetCancelled();

// Failed
source.SetException(new Exception("Failed"));

Real life example : Xamarin.Android Intents

This is commonly used for changing .NET event based APIs to Tasks based one, but I used this mechanism recently for waiting for Intents callbacks in an Xamarin.Android application.

If you’re not too familiar with Intents, read first the Android documentation. Basically, its a message that can be broadcasted at a system wide level, and several subscribers (activities, services, system) can react to those messages.

For this example, we want to pick an image file from a Xamarin.Android Activity.

private TaskCompletionSource<Android.Net.Uri> source;

public Task<Android.Net.Uri> PickImageAsync()
{
	if (this.source != null && !this.source.Task.IsFaulted && !this.source.Task.IsCanceled)
		return this.source.Task;

	this.source = new TaskCompletionSource<Android.Net.Uri>();
	var task = this.source.Task;

	try
	{
		var imageIntent = new Intent();
		imageIntent.SetType("image/*");
		imageIntent.SetAction(Intent.ActionGetContent);
		var i = Intent.CreateChooser(imageIntent, "Select a photo");
		StartActivityForResult(i, 0);
	}
	catch (Exception e)
	{
		this.source.SetException(e);
		this.source = null;
	}

	return task;
}

protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
	base.OnActivityResult(requestCode, resultCode, data);

	switch (requestCode)
	{
		case 0:
			if (resultCode == Result.Ok && data?.Data != null)
			{
				this.source.SetResult(data.Data);
			}
			else if (resultCode == Result.Canceled)
			{
				this.source.SetCanceled();
			}
			else
			{
				this.source.SetException(new Exception("Failed to get a file"));
			}

			this.source = null;

			break;
	}
}

Then, the method can simply be awaited from an async method :

var file = await this.PickImageAsync();

See the complete working project on github.

Of course, you can do this with BroadcastReceiver for example and this can be improved by adding CancellationToken verification, but you should be able to go further on your own now.

I find this great to be able to respect C# paradigmes even with APIs that have not be designed for that.