Skip to content
Lesson 5 of 7

Step 1 of 5 · Reading · ~2 min

Learn

Async, Type Providers, Interop

F#'s async { ... } is a computation expression (you saw the pattern). It lets you write asynchronous code that looks sequential.

Distinction: F# Async<'T> is cold (does nothing until started). C# Task<T> is hot (starts immediately when created). This matters for composition.

Basic async:

let fetchAsync (url: string) : Async<string> =
    async {
        use client = new System.Net.Http.HttpClient()
        let! response = client.GetStringAsync(url) |> Async.AwaitTask
        return response
    }

let! unwraps an Async (like await in C#). use is using — disposes the client when done.

Running async:

let result = fetchAsync "https://example.com" |> Async.RunSynchronously

RunSynchronously blocks the current thread. Use it at the top level (e.g., main) only.

Parallel async:

let urls = ["https://a.com"; "https://b.com"; "https://c.com"]

let results =
    urls
    |> List.map fetchAsync       // List<Async<string>>
    |> Async.Parallel             // Async<string[]>  — run concurrently
    |> Async.RunSynchronously     // string[]

Async.Parallel schedules all of them concurrently, returns an array when all complete. Async.Sequential runs in order.

Cancellation:

let cts = new System.Threading.CancellationTokenSource()
Async.Start(longTask, cts.Token)
// ... later ...
cts.Cancel()

F# 6+ added task { ... } for direct C# Task interop — same syntax, hot semantics.

When to use which:

  • async {} for F#-idiomatic code, cold composition, cancellation control
  • task {} when interfacing with C# libraries that expect Tasks
  • Both for HTTP, file I/O, DB calls, anything I/O-bound

Order comes from the array, not from the printing. Async.Parallel returns results in the order of the workflows you handed it, however the scheduler happened to interleave them. So compute inside the workflow and print outside it:

let doubleAsync n = async { do! Async.Sleep 1; return n * 2 }

// WRONG — printfn inside the workflow runs in completion order:
//   async { do! Async.Sleep 1; printfn "%d" (n * 2) }
//   for [1;2;3] this really does print 4, 2, 6 on a bad day.

let results =
    [1..3] |> List.map doubleAsync |> Async.Parallel |> Async.RunSynchronously
for r in results do printfn "%d" r

// 2
// 4
// 6

Anti-patterns:

  • Async.RunSynchronously inside another async — defeats the point
  • Forgetting to await — let result = fetchAsync url gives you the Async, not the value
  • Catching exceptions outside async — wrap in try/with inside the workflow
  • Printing from inside a workflow you are about to run in parallel — the output arrives in completion order, and the ordering bug is intermittent, so it will pass on your machine and fail on the grader
Up nextType ProvidersAsync, Type Providers, Interop

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…