Interop with .NET TPL
TPL : Task Parallel Library
Interaction with .NET libraries
Asynchronous libraries in .NET and the async
/await
C♯ pattern:
→ Based on TPL and the Task
type
Gateways with asynchronous worflow F♯ :
Async.AwaitTask
andAsync.StartAsTask
functionstask {}
block
Gateway functions
Async.AwaitTask: Task<'T> -> Async<'T>
→ Consume an asynchronous .NET library in async
block
Async.StartAsTask: Async<'T> -> Task<'T>
→ Launch an async calculation as a Task
let getValueFromLibrary param = async {
let! value = DotNetLibrary.GetValueAsync param |> Async.AwaitTask
return value
}
let computationForCaller param =
async {
let! result = getAsyncResult param
return result
}
|> Async.StartAsTask
task {}
block
task {}
blockAllows to consume an asynchronous .NET library directly, using a single
Async.AwaitTask
rather than 1 for each async method called.
💡 Available since F♯ 6 (before, we need Ply package nuget)
task {
use client = new System.Net.Http.HttpClient()
let! response = client.GetStringAsync("https://www.google.fr/")
response.Substring(0, 300) |> printfn "%s"
} // Task<unit>
|> Async.AwaitTask // Async<unit>
|> Async.RunSynchronously
Async
vs Task
Async
vs Task
1. Calculation start mode
Task
= hot tasks → calculations started immediately❗
Async
= task generators = calculation specification, independent of startup
→ Functional approach: no side-effects or mutations, composability
→ Control of startup mode: when and how 👍
2. Cancellation support
Task
: by adding a CancellationToken
parameter to async methods
→ Forces manual testing if token is canceled = tedious + error prone❗
Async
: automatic support in calculations - token to be provided at startup 👍
Recommendation for async function in F♯
C♯ async
applied at a method level
≠ F♯ async
defines an async block, not an async function
☝ Recommendation:
» Put the entire body of the async function in an async
block.
// ❌ Avoid
let workThenWait () =
Thread.Sleep(1000)
async { do! Async.Sleep(1000) } // Async only in this block 🧐
// ✅ Prefer
let workThenWait () = async {
Thread.Sleep(1000)
printfn "work"
do! Async.Sleep(1000)
}
Pitfalls of the async
/await
C♯ pattern
async
/await
C♯ patternPitfall 1 - Really asynchronous?
In C♯: method async
remains on the calling thread until the 1st await
→ Misleading feeling of being asynchronous throughout the method
async Task WorkThenWait() {
Thread.Sleep(1000); // ⚠️ Blocks calling thread !
await Task.Delay(1000); // Really async from here 🤔
}
Pitfall 2 - Omit the await
await
async Task PrintAfterOneSecond(string message) {
await Task.Delay(1000);
Console.WriteLine($"[{DateTime.Now:T}] {message}");
}
async Task Main() {
PrintAfterOneSecond("Before"); // ⚠️ Missing `await`→ warning CS4014
Console.WriteLine($"[{DateTime.Now:T}] After");
await Task.CompletedTask;
}
Compiles but returns unexpected result: After before Before❗
[11:45:27] After
[11:45:28] Before
This pitfall is also present in F♯:
let printAfterOneSecond message = async {
do! Async.Sleep 1000
printfn $"[{DateTime.Now:T}] {message}"
}
async {
printAfterOneSecond "Before" // ⚠️ Missing `do!` → warning FS0020
printfn $"[{DateTime.Now:T}] After"
} |> Async.RunSynchronously
Compiles but returns another unexpected result: no Before at all ⁉️
[11:45:27] After
Compilation warnings
The previous examples compile but with big warnings!
C♯ warning CS4014 message:
Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the
await
operator...
F♯ warning FS0020 message:
The result of this expression has type
Async<unit>
and is implicitly ignored. Consider usingignore
to discard this value explicitly...
☝ Recommendation: be sure to always handle this type of warnings! This is even more crucial in F♯ where compilation can be tricky.
Async.Parallel
Thread-safety
Impure functions can be not thread-safe, for instance if they mutate a shared object like the Console
.
It's possible to make them thread-safe using the lock
function:
open System
open System.Threading
let printColoredMessage =
let lockObject = obj ()
fun (color: ConsoleColor) (message: string) ->
lock lockObject (fun () ->
Console.ForegroundColor <- color
printfn $"%s{message} (thread ID: %i{Thread.CurrentThread.ManagedThreadId})"
Console.ResetColor())
[ ConsoleColor.Red
ConsoleColor.Green
ConsoleColor.Blue ]
|> List.randomShuffle
|> List.indexed
|> List.map (fun (i, color) -> async { printColoredMessage color $"Message {i}" })
|> Async.Parallel
|> Async.RunSynchronously
Results in the console (example):

Last updated
Was this helpful?