F# Guide — Task: .NET Task-Based Asynchronous Programming
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# task computation expressions (available in F# 6+) provide direct support for .NET System.Threading.Tasks.Task, enabling seamless interop with C# async code and libraries using a familiar async/await-style syntax.
What You'll Learn
- Task computation expressions
- Task vs Async comparison
- Converting between Task and Async
- Error handling in tasks
- Cancellation support
Why It Matters
Tasks are the standard .NET async model. F# task CE enables direct interop with C# libraries, ASP.NET Core, and the broader .NET ecosystem. Durga Antivirus Pro uses tasks for its .NET integration layer.
Real-World Use
ASP.NET Core controllers, Entity Framework queries, Azure SDK calls, and any .NET library using Task-based async.
flowchart LR
A["Task"] --> B["task { }"]
B --> C["let! binding"]
C --> D["Interop"]
D --> E["Best Practices"]
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
Task Computation Expression
// Basic task
let fetchDataAsync url = task {
let! html = httpClient.GetStringAsync(url)
return html.Length
}
// Multiple steps
let processDataAsync url = task {
let! data = fetchDataAsync url
let! saved = saveToDatabaseAsync data
return saved
}
Task vs Async
// Async (F# native)
let asyncWork = async {
let! result = someAsyncWork
return result * 2
}
// Task (F# 6+)
let taskWork = task {
let! result = someTaskWork
return result * 2
}
// Differences:
// - Tasks start immediately on creation
// - Async computations are lazy, start on demand
// - Tasks integrate with C# async/await
// - Tasks can be awaited with let! inside task CE
Interop with C#
// Calling C# async methods
let callCsharpLibraryAsync = task {
let client = new System.Net.Http.HttpClient()
let! response = client.GetStringAsync("http://example.com")
return response.Length
}
// Exposing tasks to C#
let methodReturningTask x = task {
do! Task.Delay 100
return x * 2
}
// C# sees: Task<int> methodReturningTask(int x)
Error Handling
let safeTask = task {
try
let! result = riskyOperationAsync()
return Ok result
with
| :? System.Net.Http.HttpRequestException as ex ->
return Error (sprintf "HTTP error: %s" ex.Message)
| ex ->
return Error (sprintf "Error: %s" ex.Message)
}
// Task with cancellation
let cancellableTask ct = task {
for i in 1..100 do
ct.ThrowIfCancellationRequested()
do! Task.Delay(100, ct)
return 42
}
Converting Between Task and Async
// Async to Task
let asyncComp = async { return 42 }
let taskFromAsync: Task<int> = asyncComp |> Async.StartAsTask
// Task to Async
let taskComp = Task.Run(fun () -> 42)
let asyncFromTask: Async<int> = taskComp |> Async.AwaitTask
// task CE can await both
let mixed = task {
let! asyncResult = asyncComp |> Async.StartAsTask
let! taskResult = taskComp
return asyncResult + taskResult
}
Parallel Tasks
let parallelTasks = task {
let task1 = fetchDataAsync "http://a.com"
let task2 = fetchDataAsync "http://b.com"
// Tasks start immediately (not lazy)
let! result1 = task1
let! result2 = task2
return result1 + result2
}
// Better parallel with WhenAll
let parallelWhenAll = task {
let urls = ["http://a.com"; "http://b.com"; "http://c.com"]
let! results =
urls
|> List.map fetchDataAsync
|> Task.WhenAll
return results |> Array.sum
}
Resource Management
let readFileAsync path = task {
use stream = new System.IO.FileStream(path, System.IO.FileMode.Open)
use reader = new System.IO.StreamReader(stream)
let! content = reader.ReadToEndAsync()
return content
}
// use! for async disposal
let processResourceAsync = task {
use! resource = acquireResourceAsync()
let! result = resource.DoWorkAsync()
return result
}
Common Mistakes
1. Blocking on tasks
Calling .Result or .Wait() blocks threads. Always use let! inside task CE.
2. Tasks as lazy values
Unlike Async, tasks start executing immediately upon creation. Create them just before awaiting.
3. Forgetting cancellation tokens
Long-running tasks should accept and respect cancellation tokens for responsiveness.
4. Mixing task and async without conversion
Don't use let! on an F# async inside task CE. Convert with Async.StartAsTask first.
5. Task.Run overhead
Use task { } for async I/O work. Use Task.Run only for CPU-bound background work.
Practice Questions
1. How is task different from async? Tasks start immediately upon creation. Async computations are lazy. Tasks integrate directly with .NET Task-based async.
2. How do you call C# async methods in F#?
Use task { let! result = csharpMethodAsync() ... }. The task CE directly awaits .NET tasks.
3. What happens if a task throws?
The exception is captured in the task. When awaited with let!, it's rethrown. Use try-with inside task CE to handle it.
Challenge: Write a task-based function that downloads two URLs in parallel and returns the combined content length.
FAQ
{{< faq question="Should I use task or async?" >}} Use task for .NET interop, ASP.NET Core, and when C# code will consume your async methods. Use async for F#-only code and when you need lazy computations. {{< /faq >}}
{{< faq question="Are tasks hot or cold?" >} Tasks are hot (start immediately). Async computations are cold (start on demand). This is a fundamental difference. {{< /faq >}}
{{< faq question="Can I cancel a task?" >}
Yes. Pass a CancellationToken to your task and check ct.ThrowIfCancellationRequested() periodically.
{{< /faq >}}
{{< faq question="What is Task.WhenAll?" >} A .NET method that creates a task completing when all provided tasks complete. Useful for parallel execution. {{< /faq >}}
{{< faq question="Can I use async disposables with tasks?" >}
Yes. use! in task CE disposes resources asynchronously when they implement IAsyncDisposable.
{{< /faq >}}
Mini Project
Build an async web API client using task CE:
type JsonPlaceholderClient() =
let http = new System.Net.Http.HttpClient()
member this.GetUserAsync userId = task {
let! response = http.GetStringAsync($"https://jsonplaceholder.typicode.com/users/{userId}")
return response
}
member this.GetUsersAsync() = task {
let! response = http.GetStringAsync("https://jsonplaceholder.typicode.com/users")
return response
}
member this.GetAllAsync userIds = task {
let tasks = userIds |> List.map this.GetUserAsync
let! results = Task.WhenAll(tasks)
return results |> Array.toList
}
What's Next
Now that you understand tasks, explore MailboxProcessor for agent-based concurrency.
| Topic | Description | Link |
|---|---|---|
| F# MailboxProcessor | Agent-based concurrency | {{< ref "20-mailbox-processor" >}} |
| F# Queries | Query expressions | {{< ref "21-queries" >}} |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro