Contents
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.
Thank you Pragmateek for this great post!
The parell with Javascript Promise feature allows to better understand how C# ContinueWith works.
To add again some syntactic sugar, the series of await statements can be improved by using method WhenAll(DoFirstThing,DoSecondThing,DoThirdThing) of Task class.
WhenAll() returns a Task that completes when all the tasks in its argument list have completed.
Great Job
Thanks for your feedback. 🙂
Indeed
WhenAll
could be a good fit for other use-cases but here I wanted to keep the sequential aspect.