A basic use-case for Task Unwrap

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.

2 thoughts on “A basic use-case for Task Unwrap

  1. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *

Prove me you\'re human :) *