Last time we looked at the Async class, and examined some of its core functions. This time we will be looking at using some Task Parallel Library (TPL) classes, namely Task<T>, and Task. We will also examine how the Async module can be used in conjunction with TPL.
I do not have enough time in this post to go through all the nitty gritty details of TPL, but I will just mention a few key points
- TPL uses a Task<T> to represent a asynchronous operation that will return a value T in this case (yes a generic so anything your heart desires)
- TPL uses a Task to represent a asynchronous operation that doesn’t return a value. Unit in F# lingo
- In TPL there are several trigger values that cause the Task<T> to be observed. Things like Wait / WaitAll / Result will also cause the tasks to be observed. These are however blocking operations that suspend the calling thread.
- TPL may also use CancellationTokens to cancel async operations (albeit you need a bit more code in C# than you do in F# due to the fact that in C# you must constantly check the CancellationToken, which we saw in the previous post)
- Both Task<T> and Task can be waited on
- Both Task<T> and Task can run things known as continuations, which are essentially callbacks when the Task<T> / Task is done. You may schedule callback for when a Task ran to completion, or is faulted, or both, or none
- Task<T> and Task for the basis of the new async/await syntax in C#
Starting And Waiting For Task<T>
In this simple example we will show how to create a simple Task<T> that returns a boolean. We will the use the blocking Task<T>.Wait() method, to obtain the result of the Task<T>, which will be a boolean in this case.
Which when run gives the following output
We could also do this another way too which would yield the same results. We could use a continuation from the original Task<T> that is run when the original task runs to completion. Think of continuations as callbacks. Here is the code rewritten to use a continuation, remember you can have a single callback for the whole original task, or hook up specific ones for particular scenarios, which is what I have done here.
Starting And Waiting For Task<T> In A More F# Like Way
The Async class offers a couple of helpers when dealing with tasks, you may use
Here is some code that shows how you can use these
Here are the results of running the above code:
Starting And Waiting For Plain Task
Another thing you might find yourself wanting to do is a use a TPL Task. That is a Task that does not return a value, basically you have Task<T> which is a task that returns T, and Task (essentially Task void, or Task<Unit> in F# lingo), which is a task that doesn’t return a value. Task may still be waited on in C# land, but there seems to be less you can do with a standard Task (one that doesn’t return a value) in F#.
There however a few tricks you can do, the first one requires a bit of insight into multi threading anyway, which is that Task, and Task<T> for that matter both implement IAsyncResult, which is something you can wait on inside of a F# async workflow, by using Async.AwaitIAsyncResult. Here is a small example, of how you can wait on a plain Task. This example also demonstrates how you can extend the Async module to include your own user specified functions. That is pretty cool actually, C# allows extension methods (which F# also allows), but being able to just add arbitrary functions is very cool.
Anyway here is the code:
Which when run gives the following result:
Some other clever chap who maintains this blog https://gist.github.com/theburningmonk/3921623 has a slightly different take on this. Here is his version, which I also think has many merits, for example it is really nice that it will pattern match against a Faulted Task and raise an Exception
Which gives the following results when run:
Starting And Waiting For Multiple Tasks
To wait for multiple Task<T> you can use TPLs Task.WhenAll() for this, which will give you an aggregated result task, which will have a result object which contains the results from the original tasks you used in the Task.WaitAll() call.
There may well be a way that you can bend the Async.Parallel() to do the same job, but to my mind using Task.WhenAll() is by far the easiest way.
Here is some code that demonstrates this
Which when run will give the following results