Skip to content

F# Guide — Async: Asynchronous Programming with Async

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about F# Guide. We cover key concepts, practical examples, and best practices to help you master this topic.

F# async workflows provide a compositional approach to asynchronous programming where async computations are defined as values and executed explicitly, supporting cancellation and parallel composition.

What You'll Learn

  • Creating async computations
  • let!, do!, and return keywords
  • Running async computations
  • Cancellation and error handling
  • Parallel async operations

Why It Matters

Async prevents thread blocking during I/O, enabling scalable applications. Durga Antivirus Pro uses async for concurrent file scanning without thread pool exhaustion.

Real-World Use

Web API calls, database queries, file I/O, and any operation that waits on external resources benefit from async.

flowchart LR
    A["Async Workflows"] --> B["async { }"]
    B --> C["let! binding"]
    C --> D["Execution"]
    D --> E["Parallelism"]
    A:::current --> B
    style A fill:#2563eb,stroke:#2563eb,color:#fff
    style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b

Basic Async

// Define an async computation
let asyncHello = async {
    do! Async.Sleep 1000
    return "Hello after 1 second"
}

// Run it synchronously (blocks current thread)
let result = asyncHello |> Async.RunSynchronously
// "Hello after 1 second"

let! and do!

// let! : bind async result to a value
// do! : run async operation, ignore result

let fetchData url = async {
    let! html = fetchUrlAsync url
    let length = String.length html
    return length
}

let logAndProcess url = async {
    do! logAsync ("Processing " + url)
    let! result = fetchData url
    return result
}

Running Async Computations

let computation = async { return 42 }

// Block current thread
computation |> Async.RunSynchronously

// Start on thread pool (fire and forget)
computation |> Async.Start

// Start as task
computation |> Async.StartAsTask

// Start with continuation
async {
    let! result = computation
    printfn "Got %d" result
} |> Async.Start

Async File I/O

open System.IO

let readFileAsync path = async {
    use stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true)
    use reader = new StreamReader(stream)
    let! content = reader.ReadToEndAsync() |> Async.AwaitTask
    return content
}

let writeFileAsync path content = async {
    use writer = new StreamWriter(path)
    do! writer.WriteAsync(content) |> Async.AwaitTask
}

Parallel Async

// Run multiple async operations in parallel
let fetchAll urls =
    urls
    |> List.map fetchUrlAsync
    |> Async.Parallel

let results = fetchAll ["http://a.com"; "http://b.com"]
              |> Async.RunSynchronously
// results is an array of all responses

// Sequential (one at a time)
let fetchSequential urls = async {
    let mutable results = []
    for url in urls do
        let! result = fetchUrlAsync url
        results <- result :: results
    return List.rev results
}

Cancellation

// Support cancellation
let cancellableWork = async {
    let! ct = Async.CancellationToken
    for i in 1..100 do
        if ct.IsCancellationRequested then
            return None
        do! Async.Sleep 100
    return Some 42
}

// Cancel with CancellationTokenSource
let cts = new System.Threading.CancellationTokenSource()
Async.Start(cancellableWork, cts.Token)
cts.Cancel()  // Cancels the operation

Error Handling

let safeAsync = async {
    try
        let! result = riskyOperation()
        return Ok result
    with
    | :? System.Net.WebException as ex ->
        return Error (sprintf "Network error: %s" ex.Message)
    | ex ->
        return Error (sprintf "Unexpected: %s" ex.Message)
}

// Or use try-with inside async
let withTimeout timeout work = async {
    let! child = Async.StartChild(work, timeout)
    try
        let! result = child
        return Some result
    with :? System.TimeoutException ->
        return None
}

Common Mistakes

1. Mixing sync and async

Calling Async.RunSynchronously inside an async block defeats the purpose. Use let! instead.

2. Forgetting to start

Defining async { } just creates a computation. It doesn't run until explicitly started or awaited.

3. Thread pool starvation

Creating too many async operations without limiting parallelism can exhaust the thread pool.

4. Ignoring cancellation

Long-running async operations should check the cancellation token regularly.

5. Blocking in async context

Avoid Thread.Sleep, Task.Wait, or lock inside async workflows.

Practice Questions

1. What does let! do in async? It asynchronously awaits the result of another async computation without blocking the thread.

2. How do you run async operations in parallel? Use Async.Parallel to run a sequence of async operations concurrently.

3. What is the difference between Async.Start and Async.RunSynchronously? Async.Start fires and forgets on the thread pool. Async.RunSynchronously blocks the current thread until complete.

Challenge: Write a function that downloads multiple URLs in parallel and returns the results.

FAQ

{{< faq question="Is F# async like C# async/await?" >}} Similar but different. F# async is a computation expression. C# async/await is a language feature. F# async is more compositional. {{< /faq >}}

{{< faq question="Can I use C# async methods from F#?" >}} Yes, use Async.AwaitTask to convert a .NET Task to an F# async computation. {{< /faq >}}

{{< faq question="What is Async.CancellationToken?" >}} An async computation that binds the current cancellation token, allowing you to check for cancellation requests. {{< /faq >}}

{{< faq question="How do I convert async to Task?" >}} Use Async.StartAsTask to get a Task<'T> from an async computation. {{< /faq >}}

{{< faq question="Can async computations be retried?" >}} Yes. Write retry logic with async { } and Recursion, wrapping the operation in a try-with block. {{< /faq >}}

Mini Project

Build an async web scraper that fetches multiple pages:

let fetchUrlAsync (url: string) = async {
    let request = System.Net.WebRequest.Create(url)
    use! response = request.GetResponseAsync() |> Async.AwaitTask
    use stream = response.GetResponseStream()
    use reader = new System.IO.StreamReader(stream)
    let! content = reader.ReadToEndAsync() |> Async.AwaitTask
    return url, content.Length
}

let scrapeSites urls = async {
    let! results =
        urls
        |> List.map fetchUrlAsync
        |> Async.Parallel
    return results |> Array.toList
}

// Usage
[ "http://example.com"; "http://test.com" ]
|> scrapeSites
|> Async.RunSynchronously

What's Next

Now that you understand async, explore the Task module for .NET task integration.

Topic Description Link
F# Task Task-based async {{< ref "19-task" >}}
F# MailboxProcessor Agent-based concurrency {{< ref "20-mailbox-processor" >}}

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro