Introduction
Recently, after months without using .Net/C#, I was enhancing an existing .Net/C# WPF application leveraging the .Net Task Parallel Library (TPL).
But naively applying the JavaScript Promises patterns I had used in the previous months I was bitten by a strange issue which forced me to use the quite exotic Unwrap extension method.
This article describes the issue, explains its cause, provides a fix with Unwrap, and finally provides a more modern version with the C# 5.0 async/await paradigm.
A simple workflow in JavaScript with Promises
Here is a JavaScript implementation of a simple workflow with 3 steps, the second one simulating a delayed processing with setTimeout, using the Promise API:
function doFirstThing() { return new Promise(resolve => { console.log("First thing done") resolve() }) } function doSecondThing() { return new Promise(resolve => { setTimeout(() => { console.log("Second thing done") resolve() }, 1000) }) } function doThirdThing() { return new Promise(resolve => { console.log("Third thing done") resolve() }) } doFirstThing().then(doSecondThing).then(doThirdThing)
Here is the result once run with Node:
$ node test.js First thing done Second thing done Third thing done
A C# implementation with Tasks
Here is the same workflow implemented with C# using .Net TPL:
using System; using System.Threading.Tasks; namespace Test { class Program { static Task DoFirstThing() { return Task.Run(() => Console.WriteLine("First thing done")); } static Task DoSecondThing() { return Task.Delay(1000).ContinueWith(_ => Console.WriteLine("Second thing done")); } static Task DoThirdThing() { return Task.Run(() => Console.WriteLine("Third thing done")); } static void Main(string[] args) { DoFirstThing().ContinueWith(_ => DoSecondThing()).ContinueWith(_ => DoThirdThing()); Console.ReadLine(); } } }
Note that contrary to JavaScript Promises .Net Tasks are not started/scheduled automatically when created, hence the need to explicitly call Run.
Here is the result:
First thing done Third thing done Second thing done
As you see the third step is executed before the second one!
This is because ContinueWith creates a new Task wrapping the provided treatment which only consists in calling DoSecondThing (which itself creates the second task) which returns immediately.
ContinueWith won’t consider the resulting Task, contrary to Promise.then which handles the case of returning a Promise in a specific manner: the Promise returned by then will be resolved only when the underlying Promise will.
Unwrap to the rescue
To retrieve the JavaScript Promises behavior we need to explicitly tell the TPL we want to consider the underlying Task using Unwrap (implemented as an extension method provided by the TaskExtensions class):
DoFirstThing().ContinueWith(_ => DoSecondThing()).Unwrap().ContinueWith(_ => DoThirdThing());
Result is now consistent with JavaScript:
First thing done Second thing done Third thing done
A more modern way with await
C# 5.0 adds some syntactic sugar to ease the use of the TPL with the await operator:
await DoFirstThing(); await DoSecondThing(); await DoThirdThing();
await internally calls Unwrap and waits on the underlying Task as expected, and yields the same result.
Note that await can only be used in an async method.
Conclusion
Mapping between languages and frameworks is not always obvious but fortunately nowadays all seem to copycat each other and they end offering the same paradigms and APIs like the async/await duo you use almost in the same manner in both C# and JavaScript.