liveBook · Manning

Task computation expression

As explained in F# in Action: A Revised Edition of Get Programming with F#

A task computation expression is an F# computation expression that constructs an asynchronous .NET Task computation and supports awaiting task results without blocking the current thread.

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

How F# in Action: A Revised Edition of Get Programming with F# covers it

How deep it goes
Explains let!, do!, return, task boundaries, parallel composition, and contrasts hot Tasks with cold async workflows.
Examples
Demonstrates appending and reading files, sequencing three reads, and reading multiple files in parallel.
What it assumes
Introduces task-block syntax while assuming a need to work with Task-returning asynchronous APIs.
What you can do after
Apply it via .NET Task APIs to sequence asynchronous work and compose independent tasks with Task.WhenAll.
How it connects
Connects Task, async computation expressions, Task.WhenAll, cancellation tokens, List.map, and immutable data.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of F# in Action: A Revised Edition of Get Programming with F#. The section links point to the parts of the book it draws on.