A task computation expression, written task { ... }, builds an asynchronous .NET Task in F#. It keeps asynchronous I/O in direct-looking code: await a task result with let!, await a Task<unit> with do!, and produce the final task result with return. The work can continue without blocking the current thread. section 12.2.3 section 12.4
Use it when an operation already returns Task values, such as asynchronous file APIs. Instead of manually wiring continuations or adopting older callback styles, place the dependent operations in one task block. The expression unwraps each completed task result and wraps the final value back into a Task. section 12.2.3 section 12.2.5
Read after writing
let writeToFileAsync fileName data = task { do! System.IO.File.AppendAllTextAsync(fileName, data) let! text = System.IO.File.ReadAllTextAsync fileName return text.Length } The append operation has no useful result, so do! awaits it. Reading returns Task<string>, so let! binds the string before its length is returned. section 12.2.3
A task block can sequence several tasks, but sequencing is not parallelism. When independent work should run together, create a collection of tasks and use Task.WhenAll; its combined Task returns an array of completed results that can be processed inside a task block. section 12.2.4 section 12.4
Listing 12.3 Composing multiple tasks together in parallel
task {
let! allFiles =
[ "file1.txt"
"file2.txt"
"file3.txt" ] #1
|> List.map File.ReadAllTextAsync #2
|> Task.WhenAll #3
return
allFiles
|> Array.reduce (sprintf "%s %s") #4
}
This example combines List.map, Task.WhenAll, let!, and Array.reduce to read several files in parallel and merge their contents. section 12.2.4
task { } is a hot computation: it starts when created and normally executes once. This differs from async { }, which is cold, must be explicitly started, and needs conversion helpers to consume .NET Tasks. Task blocks also make the boundary visible: code before the block runs immediately, while the block represents the asynchronous work. section 12.2.5 section 12.2.6
Task is the general-purpose choice for much .NET-oriented asynchronous work, while async { } remains useful and common in F#. Choose deliberately when cancellation or rerunnable cold workflows matter: Tasks require cancellation tokens to be passed manually, whereas async workflows support cancellation implicitly. section 12.2.6 section 12.4
