Tutorial 3: Understanding Async/Await (In Plain English)
What do async and await mean in C#, and why does every CurlDotNet request look like await Curl.ExecuteAsync(...)? This tutorial explains asynchronous programming with everyday analogies, shows the rules you need to follow, and covers the mistakes beginners make most often.
What You’ll Learn
- What
asyncandawaitmean - Why we use them
- How to use them correctly
- Common mistakes to avoid
Prerequisites
The Restaurant Analogy
Imagine you are at a restaurant. There are two ways to get your food:
Method 1: Stand and Wait (Synchronous)
1. You order a burger
2. You stand at the counter
3. You can't do anything else
4. You just wait... and wait...
5. Finally you get your burger
6. Now you can do the next thing
Method 2: Take a Number (Asynchronous)
1. You order a burger
2. They give you a number
3. You sit down and chat with friends
4. You check your phone
5. They call your number
6. You get your burger
Async/await is Method 2. Your program does not freeze while it waits.
In Code Terms
Without Async (Program Freezes)
// Avoid this for web requests - the whole program stops while it waits
static void Main()
{
Console.WriteLine("Starting download...");
var result = FetchPage("https://httpbin.org/delay/3"); // blocks for 3 seconds
Console.WriteLine("Finally done!");
}
With Async (Program Stays Responsive)
static async Task Main() // Note: async Task
{
Console.WriteLine("Starting download...");
var result = await FetchPageAsync("https://httpbin.org/delay/3"); // does not block the thread
Console.WriteLine("Done!");
}
The Golden Rules
Rule 1: Async Methods End with “Async”
// Synchronous versions (avoid these for web requests)
Curl.Execute("curl https://httpbin.org/get");
File.ReadAllText("data.txt");
// Asynchronous versions (use these)
await Curl.ExecuteAsync("curl https://httpbin.org/get");
await File.ReadAllTextAsync("data.txt");
Rule 2: Always Use ‘await’ with Async Methods
// WRONG - missing await; result is a Task, not a CurlResult
var result = Curl.ExecuteAsync("curl https://httpbin.org/get");
// RIGHT - with await
var result = await Curl.ExecuteAsync("curl https://httpbin.org/get");
Rule 3: Methods Using ‘await’ Must Be ‘async’
// WRONG - method not marked async, so 'await' is a compile error
static Task<string> GetData()
{
var result = await Curl.ExecuteAsync("curl https://httpbin.org/get");
return result.Body;
}
// RIGHT - method marked async
static async Task<string> GetData()
{
var result = await Curl.ExecuteAsync("curl https://httpbin.org/get");
return result.Body;
}
Real Examples with CurlDotNet
Example 1: Simple Async Call
using System;
using System.Threading.Tasks;
using CurlDotNet;
class Program
{
static async Task Main() // async Task for Main
{
var result = await Curl.ExecuteAsync("curl https://api.github.com");
Console.WriteLine($"Got {result.Body.Length} characters");
}
}
Example 2: Multiple Async Calls (One at a Time)
static async Task Main()
{
// These happen one after another
var result1 = await Curl.ExecuteAsync("curl https://api.github.com");
Console.WriteLine("Got first response");
var result2 = await Curl.ExecuteAsync("curl https://httpbin.org/get");
Console.WriteLine("Got second response");
}
Example 3: Multiple Async Calls (All at Once)
static async Task Main()
{
// Start both at the same time
var task1 = Curl.ExecuteAsync("curl https://api.github.com");
var task2 = Curl.ExecuteAsync("curl https://httpbin.org/get");
// Wait for both to complete
var results = await Task.WhenAll(task1, task2);
Console.WriteLine($"Got {results.Length} responses");
}
CurlDotNet also has a shortcut for this: await Curl.ExecuteManyAsync("curl https://api.github.com", "curl https://httpbin.org/get") returns a CurlResult[]. See Tutorial 13: Parallel Requests.
What’s Really Happening?
When you use await:
- Request starts - the HTTP request begins
- Control returns - your program can do other things
- Request completes - the server responds
- Code resumes - your code continues from the
awaitline
Like ordering pizza:
- You call and order (start request)
- You watch TV while waiting (program does other things)
- The doorbell rings (request completes)
- You eat pizza (code continues)
Async vs Sync Comparison
Synchronous (Blocking)
// This freezes your program for the whole duration
public void FetchFiles()
{
var file1 = FetchFile("file1.zip"); // wait 5 seconds...
var file2 = FetchFile("file2.zip"); // wait 5 seconds...
var file3 = FetchFile("file3.zip"); // wait 5 seconds...
// Total time: 15 seconds, program frozen the whole time
}
Asynchronous (Non-Blocking)
// This does not freeze, and the downloads overlap
public async Task FetchFilesAsync()
{
var task1 = Curl.DownloadAsync("https://httpbin.org/bytes/1024", "file1.bin");
var task2 = Curl.DownloadAsync("https://httpbin.org/bytes/1024", "file2.bin");
var task3 = Curl.DownloadAsync("https://httpbin.org/bytes/1024", "file3.bin");
await Task.WhenAll(task1, task2, task3);
// Total time: roughly the slowest single download
}
Common Mistakes
Mistake 1: Forgetting await
// WRONG - this does not wait for the result
static async Task Main()
{
var resultTask = Curl.ExecuteAsync("curl https://httpbin.org/get");
Console.WriteLine(resultTask.Body); // compile error: Task has no Body
}
// RIGHT - use await to get the actual result
static async Task Main()
{
var result = await Curl.ExecuteAsync("curl https://httpbin.org/get");
Console.WriteLine(result.Body);
}
Mistake 2: Using .Result or .Wait()
// WRONG - can deadlock in UI and ASP.NET apps
static void Main()
{
var result = Curl.ExecuteAsync("curl https://httpbin.org/get").Result;
}
// RIGHT - use async/await all the way
static async Task Main()
{
var result = await Curl.ExecuteAsync("curl https://httpbin.org/get");
}
If you really need a synchronous call (a console script, for example), use the synchronous API instead: var result = Curl.Execute("curl https://httpbin.org/get");. It blocks the calling thread until the transfer ends, so keep it out of UI event handlers and classic ASP.NET request code.
Mistake 3: Not Making the Method Async
// WRONG - method not async
static Task<string> GetData()
{
var result = await Curl.ExecuteAsync("curl https://httpbin.org/get"); // error
return result.Body;
}
// RIGHT - method is async
static async Task<string> GetData()
{
var result = await Curl.ExecuteAsync("curl https://httpbin.org/get");
return result.Body;
}
Interactive Example
Try this and compare the two timings. https://httpbin.org/delay/1 waits one second before responding.
using System.Diagnostics;
using CurlDotNet;
var stopwatch = Stopwatch.StartNew();
await Curl.ExecuteAsync("curl https://httpbin.org/delay/1");
await Curl.ExecuteAsync("curl https://httpbin.org/delay/1");
Console.WriteLine($"Sequential: {stopwatch.ElapsedMilliseconds}ms (about 2 seconds)");
stopwatch.Restart();
await Task.WhenAll(
Curl.ExecuteAsync("curl https://httpbin.org/delay/1"),
Curl.ExecuteAsync("curl https://httpbin.org/delay/1"));
Console.WriteLine($"Parallel: {stopwatch.ElapsedMilliseconds}ms (about 1 second)");
Quick Reference
| Keyword | What It Does | When to Use |
|---|---|---|
async |
Marks a method as asynchronous | When the method contains await |
await |
Waits for an async operation | Before any ...Async() method |
Task |
Represents an ongoing operation | Return type for async methods |
Task<T> |
Ongoing operation with a result | When the async method returns a value |
Simple Rules to Remember
- See
Asyncin the method name? Useawait - Using
awaitin a method? Mark the methodasync - Method is
async? ReturnTaskorTask<T> - Multiple operations? Consider
Task.WhenAll()orCurl.ExecuteManyAsync
FAQs
Q: Why not just use synchronous methods?
A: Your application would freeze during network requests. For a quick script that is acceptable (Curl.Execute exists for that), but for apps and services use await.
Q: Can I use async without await? A: Technically yes, but you lose the benefit and the compiler warns you.
Q: Is async/await faster? A: A single request takes the same time. The win is that your program stays responsive and can overlap several requests.
Q: Do I always need async/await? A: For network requests, file I/O, and database calls, yes. For simple calculations, no.
Summary
- Async = non-blocking (does not freeze)
- Await = wait for the result without blocking the thread
- Always use together =
await+...Asyncmethods - Mark methods =
async Taskwhen usingawait - Better user experience = responsive applications
Next Steps
- Next tutorial: Your First Request
- Practice: try the interactive example above
- Experiment: what happens without
await?
Still confused? That is normal. Try the examples and it will click. Check common issues if you get stuck.