C# async tasks in C++

The C# way

Visual Studio 2012 introduced a simplified approach to asynchronous programming with async and await keywords. By moving all the difficult work to the compiler your code becomes more readable and easier to maintain by resembling synchronous code flow. So loading image from assets and displaying it might look like this.

try 
{
	var imgFile = await Package.Current.
			InstalledLocation.GetFileAsync("Assets\\image.png");
			
	using(var imgStream = await imgFile.OpenReadAsync())
	{
		var bitmap = new BitmapImage();
		bitmap.SetSource(imgStream);
		image.ImageSource = bitmap; // ImageBrush object
	}
}
catch (Exception e)
{
	await new MessageDialog(e.Message, "ERROR").ShowAsync();
}

Nice and simple but that’s C#, what about C++?

The C++ way

using namespace concurrency;

auto installLocation = Package::Current->InstalledLocation;

create_task(installLocation->GetFileAsync(L"Assets\\image.png"))
.then([](StorageFile^ imgFile)
{
	return create_task(imgFile->OpenReadAsync());
}, task_continuation_context::use_current())
.then([this](IRandomAccessStreamWithContentType^ imgStream)
{
	auto bitmap = ref new BitmapImage();
	bitmap->SetSource(imgStream);
	image->ImageSource = bitmap;
});

Well, that’s a little different. It certainly doesn’t look like synchronous code but it isn’t that bad, right? You could also nest new tasks lambdas instead of using task_continuation_context like so.

using namespace concurrency;

auto installLocation = Package::Current->InstalledLocation;

create_task(installLocation->GetFileAsync(L"Assets\\image.png"))
.then([this](StorageFile^ imgFile)
{
	create_task(imgFile->OpenReadAsync())
	.then([this](IRandomAccessStreamWithContentType^ imgStream)
	{
		auto bitmap = ref new BitmapImage();
		bitmap->SetSource(imgStream);
		image->ImageSource = bitmap;
	});
});

However I prefer task_continuation_context. Nesting lambdas can easily lead to code beginning in the middle of the screen and you have to remember to copy/reference variables throughout whole code tree.

But wait, isn’t there still something missing here? Hmmm… Oh yes, exceptions! Silly me, how could I forget? But that’s easily fixed, right? All you need to do is put everything in one try catch block, right? No.

using namespace concurrency;

auto installLocation = Package::Current->InstalledLocation;

create_task(installLocation->GetFileAsync(L"Assets\\image.png"))
.then([](task<StorageFile^> t)
{
	try 
	{
		auto imgFile = t.get();
		return create_task(imgFile->OpenReadAsync());
	} 
	catch(Exception^ e)
	{
		create_task(ref new 
			MessageDialog(e->Message, L"ERROR")->ShowAsync());
		return create_task(
			create_async([]() -> IRandomAccessStreamWithContentType^ 
		{ 
			return nullptr; 
		}));
	}
}, task_continuation_context::use_current())
.then([this](task<IRandomAccessStreamWithContentType^> t)
{
	try 
	{
		auto imgStream = t.get();
		if(imgStream!=nullptr)
		{
			auto bitmap = ref new BitmapImage();
			bitmap->SetSource(imgStream);
			image->ImageSource = bitmap;
		}
	} 
	catch(Exception^ e)
	{
		create_task(ref new 
			MessageDialog(e->Message, L"ERROR")->ShowAsync());
	}
});

Well, that turned ugly really quickly, but what happened? The problem is control flow. In C# a method suspends its progress and yields control to caller at an await and then resumes when it’s ready, so it’s still in try and catch block. In C++ there is no pause and resume. A method performs create_task() function and moves on, leaving any try catch blocks. Then after async method is done, your lambda body is called on a thread that created the task. Since async method is called on some other thread you can’t catch any exceptions by asking for expected value. You have to ask for task object and perform get() method to propagate any exception that async method might’ve thrown. Notice that by asking for expected value the get() method is called implicitly but outside your try catch block.

There is one more important thing. If you’re using task_continuation_context you must remember to return proper task object for each exit point. If eg. you’ll forget to return task in catch block and you have default compiler settings you’ll only get compiler warning and a runtime crash in case an exception occurs. Also, since you’re required to return task object you’re basically forced to handle normal errors. If I didn’t check imgStream for nullptr I would’ve caused another exception and trying to open MessageDialog when one is already open results in a crash. With lambda nesting you don’t have to worry about it but you’re going to reach that middle of the screen really soon. This example only calls two async methods in succession. Imagine something more complicated, like downloading an image and doing some post-processing on it. Jolly.

You can get around try catch problem by eg. performing everything in your own thread. Thanks to this you can wait() without fear (calling this on main thread results in uncatchable crash) and get everything in one nice try catch block. Just don’t forget to call UI stuff on main thread.

auto window = Window::Current;

std::thread([this, window]
{
	using namespace concurrency;
	
	try 
	{
		auto installLocation = Package::Current->InstalledLocation;
		auto fileTask = create_task(
			installLocation->GetFileAsync(L"Assets\\image.png"));
		fileTask.wait();
		auto streamTask = create_task(
			fileTask.get()->OpenReadAsync());
		streamTask.wait();
		auto imgStream = streamTask.get();
		window->Dispatcher->RunAsync(CoreDispatcherPriority::Normal, 
			ref new DispatchedHandler([this, imgStream]
		{
			auto bitmap = ref new BitmapImage();
			bitmap->SetSource(imgStream);
			image->ImageSource = bitmap;
		}));
	} 
	catch(Exception^ e)
	{
		create_task(ref new 
			MessageDialog(e->Message, L"ERROR")->ShowAsync()); 
			//this doesn't have to be called from main thread
	}
}).detach();

Conclusion

Developing for Microsoft platforms in C++ you may feel unwelcome. Everyone is writing in C#, examples are scarce and code gets ugly. This is especially problematic in Windows Runtime API (Windows Phone and metro style applications) where virtually every other method is async one. But remember, you have whole C++ standard at your disposal. Try temaplating async tasks into prettier syntax. Check if you can’t get the same effect without using Microsoft API. Get creative, that’s why C++ is such a nice language. It gives you more freedom than other ones. Sure, it’s easier to break things but you won’t get up without falling.