Click here to Skip to main content
Click here to Skip to main content
Technical Blog

Tagged as

F# 28 : Integrating With Task Parallel Library

, 20 May 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
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, and Task. We will also examine how the Async module can be used in conjunction with TPL.  TPL Primer I do not have enough [&#8

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.

TPL Primer

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.

open System
open System.Threading
open System.Threading.Tasks

[<EntryPoint>]
let main argv =

let work() =
for i in 0 .. 2 do
printfn "Work loop is currently %O" i |> ignore
Thread.Sleep(1000)
false

printfn "Starting task that returns a value" |> ignore
let task = Task.Factory.StartNew<bool>((fun () -> work()), TaskCreationOptions.LongRunning)
let result = task.Result
printfn "Task result is %O" result

Console.ReadLine() |> ignore

//return 0 for main method
0

Which when run gives the following output

image

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.

open System
open System.Threading
open System.Threading.Tasks

[<EntryPoint>]
let main argv =

let work() =
for i in 0 .. 2 do
printfn "Work loop is currently %O" i |> ignore
Thread.Sleep(1000)
false

printfn "Starting task that returns a value" |> ignore
let task = Task.Factory.StartNew<bool>((fun () -> work()), TaskCreationOptions.LongRunning)
task.ContinueWith((fun (antecedant : Task<bool>) -> printfn "Task result is %O" antecedant.Result),
TaskContinuationOptions.OnlyOnRanToCompletion) |> ignore

Console.ReadLine() |> ignore

//return 0 for main method
0

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

  • Async.StartAsTask
  • Async.AwaitTask

Here is some code that shows how you can use these

open System
open System.Threading
open System.Threading.Tasks

[<EntryPoint>]
let main argv =

let work = async {
for i in 0 .. 2 do
printfn "Work loop is currently %O" i |> ignore
do! Async.Sleep(1000)
return "task is completed " + DateTime.Now.ToLongTimeString()
}

printfn "Starting task that returns a value" |> ignore

let asynWorkflow = async {
//NOTE : Async.StartAsNewTask doesn't like TaskCreationOptions.LongRunning
let task = Async.StartAsTask((work))
let! result = Async.AwaitTask(task)
return result
}

let finalResult = Async.RunSynchronously asynWorkflow
printfn "Task result is : %O" finalResult

Console.ReadLine() |> ignore

//return 0 for main method
0

Here are the results of running the above code:

image

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:

open System
open System.Threading
open System.Threading.Tasks

//This extends the Async module to add the
//AwaitTaskVoid function, which will now appear
//in intellisense
module Async =
let AwaitVoidTask : (Task -> Async<unit>) =
Async.AwaitIAsyncResult >> Async.Ignore

[<EntryPoint>]
let main argv =

let theWorkflow(delay :int) = async {
printfn "Starting workflow at %O" (DateTime.Now.ToLongTimeString())
do! Task.Delay(delay) |> Async.AwaitVoidTask
printfn "Ending workflow at %O" (DateTime.Now.ToLongTimeString())
}

Async.RunSynchronously (theWorkflow(2000))

Console.ReadLine() |> ignore

//return 0 for main method
0

Which when run gives the following result:

image

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

open System
open System.Threading
open System.Threading.Tasks

//This extends the Async module to add the
//AwaitTaskVoid function, which will now appear
//in intellisense
module Async =
let inline awaitPlainTask (task: Task) =
// rethrow exception from preceding task if it fauled
let continuation (t : Task) : unit =
match t.IsFaulted with
| true -> raise t.Exception
| arg -> ()
task.ContinueWith continuation |> Async.AwaitTask

let inline startAsPlainTask (work : Async<unit>) =
Task.Factory.StartNew(fun () -> work |> Async.RunSynchronously)

[<EntryPoint>]
let main argv =

let sleepy = async {
do! Async.Sleep(5000) // sleep for 5 seconds
printfn "awake"
}

let sleepy2 = async {
do! sleepy |> Async.startAsPlainTask |> Async.awaitPlainTask
printfn "feeling sleepy again…"
}

//call the workflows
sleepy |> Async.startAsPlainTask |> ignore
sleepy2 |> Async.Start |> ignore

Console.ReadLine() |> ignore

//return 0 for main method
0

Which gives the following results when run:

image

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

open System
open System.Threading
open System.Threading.Tasks

[<EntryPoint>]
let main argv =

let work(msg) =
for i in 0 .. 2 do
printfn "%O : Work loop is currently %O\r\n" msg i |> ignore
Thread.Sleep(1000)
"Task 1 done " + (DateTime.Now.ToLongTimeString())

let taskRunner(msg) =
printfn "Starting %O that returns a value %O\r\n" msg (DateTime.Now.ToLongTimeString()) |> ignore
Task.Factory.StartNew<string>((fun () -> work(msg)), TaskCreationOptions.LongRunning)


let task1 = taskRunner("task1")
Thread.Sleep(2000)
let task2 = taskRunner("task2")
Thread.Sleep(2000)
let task3 = taskRunner("task3")

let resultsOfAllTask = Task.WhenAll([task1;task2;task3])

printfn "Task1 result: %O\r\nTask2 result: %O\r\nTask3 result: %O"
resultsOfAllTask.Result.[0]
resultsOfAllTask.Result.[1]
resultsOfAllTask.Result.[2]

Console.ReadLine() |> ignore

//return 0 for main method
0

Which when run will give the following results

image

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Sacha Barber
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)
 
- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence
 
Both of these at Sussex University UK.
 
Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web03 | 2.8.141022.2 | Last Updated 20 May 2014
Article Copyright 2014 by Sacha Barber
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid