Tuesday 19 September 2017

Promises and Tasks

.Net Tasks and JavaScript Promises are rather similar, as both are used for the same. They hold a value that will be available at some point, moment at which they will invoke some code that we've provided to them as a continuation. There are differences though. In the multithreaded world of .Net the creation of a promise can involve the execution of another thread (when we do Task.Run for example), this is not the case with Promises, as at the Javascript level everything is single-threaded. Another difference is the parameter passed by to the (callback) function that will be executed as a continuation. While Promise.prototype.then will pass just the result of the precedent Promise, Task.ContinueWith will pass the Task itself. This stems from the fact that while we can access the result of a Task via the (blocking) Task.Result property, the only way to access the result of a Promise is by the Promise itself passing that result to us.

I've found another not so obvious difference that I'll share here. In .Net there was only a 2 years gap between the introduction of Tasks (.Net 4.0 in 2010) and the arrival of the magic async/await pair (C# 5 in 2012). In JavaScript the gap between Promises and the introduction of ES7 async/await in some environments (let's put aside libraries that simulated it via iterators) has been much bigger. This means that in .Net the exposure of programmers to ContinueWith has been rather low when compared to how much Javascript developers have dealt with then. To work with "then" we need to clearly interiorize this: Given a Promise P1, when we invoke then on it and obtain a P2 promise, if the callback/handler function that we have passed to then returns another promise (P3), then P2 will not be resolved until when P3 is resolved. This allows us to chain calls to "then" rather than nesting them. I mean (from Exploring ES6):


asyncFunc1()
.then(function (value1) {
    asyncFunc2()
    .then(function (value2) {
        ···
    });
})


The flat version looks like this:


asyncFunc1()
.then(function (value1) {
    return asyncFunc2();
})
.then(function (value2) {
    ···
})


The above is well explained in MDN:

A Promise in the pending status. The handler function (onFulfilled or onRejected) gets then called asynchronously (as soon as the stack is empty). After the invocation of the handler function, if the handler function:
...
• returns another pending promise object, the resolution/rejection of the promise returned by then will be subsequent to the resolution/rejection of the promise returned by the handler. Also, the value of the promise returned by then will be the same as the value of the promise returned by the handler.

In .NET the behaviour of ContinueWith when the delegate passed to it returns in turn a Task is not so straightforward. This internal Task can be either an Attached Child Task or a Detached Nested Task. By default it'll be a Detached Nested Task, meaning that the outer task does not need of this internal task to be finished to be finished on its own. So this behaviour is different from JavaScript. If we want that the completion of the outer task depends on the completion of the inner task, we have to create this inner task with the TaskCreationOptions.AttachedToParent flag. It is explained here and here.

... the Task being created registers with that parent Task as a child, leading to two additional behaviors: the parent Task won’t transition to a completed state until all of its children have completed as well, and any exceptions from faulted children will propagate up to the parent Task (unless the parent Task observes those exceptions before it completes).

I've tested it myself, but the code using Tasks is rather ugly and odd and won't upload it here. Hopefully using async/await abstracts us of all of this.

No comments:

Post a Comment