F# Guide — Async: Asynchronous Programming with Async
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