async is a modifier that marks a method as asynchronous, indicating it can contain asynchronous operations.
await is an operator that suspends the execution of an async method until the awaited task completes, without blocking the thread.
How it works:
- When
awaitis encountered, the method returns control to its caller - The thread is freed to do other work
- When the awaited task completes, execution resumes from where it left off
Example:
// Basic async/await example
public class DataService
{
// Async method must return Task, Task, or void (avoid void except for event handlers)
public async Task GetDataAsync()
{
// Simulate a network call or database operation
await Task.Delay(2000); // Non-blocking delay
return "Data retrieved successfully";
}
public async Task CalculateAsync(int x, int y)
{
// Simulate CPU-intensive work
await Task.Run(() =>
{
Thread.Sleep(1000);
});
return x + y;
}
}
// Calling async methods
public class Program
{
public static async Task Main(string[] args)
{
var service = new DataService();
Console.WriteLine("Starting async operation...");
// await suspends execution until GetDataAsync completes
string result = await service.GetDataAsync();
Console.WriteLine(result);
// Multiple async operations
int sum = await service.CalculateAsync(5, 10);
Console.WriteLine($"Sum: {sum}");
}
}
// Real-world example: Fetching data from an API
public class WeatherService
{
private readonly HttpClient httpClient = new HttpClient();
public async Task GetWeatherAsync(string city)
{
try
{
Console.WriteLine($"Fetching weather for {city}...");
// await makes the HTTP call non-blocking
string response = await httpClient.GetStringAsync(
$"https://api.weather.com/forecast?city={city}"
);
Console.WriteLine("Weather data received");
return response;
}
catch (HttpRequestException ex)
{
Console.WriteLine($"Error: {ex.Message}");
return null;
}
}
// Processing multiple cities concurrently
public async Task<List> GetWeatherForMultipleCitiesAsync(List cities)
{
var tasks = cities.Select(city => GetWeatherAsync(city));
// Wait for all tasks to complete
string[] results = await Task.WhenAll(tasks);
return results.ToList();
}
}
// Usage
var weatherService = new WeatherService();
var cities = new List { "New York", "London", "Tokyo" };
var weatherData = await weatherService.GetWeatherForMultipleCitiesAsync(cities);
Key Points:
asyncmethods should be named with theAsyncsuffix by conventionawaitcan only be used insideasyncmethodsasync voidshould be avoided (except for event handlers) - useasync Taskinstead- Exception handling works naturally with try/catch blocks
Related Resources
Thread is a lower-level construct that represents an actual OS thread. It's part of the threading infrastructure.
Task is a higher-level abstraction that represents an asynchronous operation. It doesn't necessarily map to a single thread.
Example:
// Using Thread (lower-level, more control, more overhead)
public class ThreadExample
{
public void RunWithThread()
{
Thread thread = new Thread(() =>
{
Console.WriteLine($"Thread ID: {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(2000);
Console.WriteLine("Thread work completed");
});
thread.Start();
thread.Join(); // Wait for thread to complete
}
// Creating multiple threads
public void RunMultipleThreads()
{
for (int i = 0; i < 5; i++)
{
int taskNumber = i;
Thread thread = new Thread(() =>
{
Console.WriteLine($"Thread {taskNumber} executing");
Thread.Sleep(1000);
});
thread.Start();
}
}
}
// Using Task (higher-level, better performance, easier to use)
public class TaskExample
{
public async Task RunWithTaskAsync()
{
await Task.Run(() =>
{
Console.WriteLine($"Task on Thread ID: {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(2000);
Console.WriteLine("Task work completed");
});
}
// Creating multiple tasks
public async Task RunMultipleTasksAsync()
{
var tasks = new List();
for (int i = 0; i < 5; i++)
{
int taskNumber = i;
tasks.Add(Task.Run(() =>
{
Console.WriteLine($"Task {taskNumber} executing on Thread {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(1000);
}));
}
await Task.WhenAll(tasks); // Wait for all tasks to complete
}
// Task with return value
public async Task CalculateAsync()
{
return await Task.Run(() =>
{
Thread.Sleep(1000);
return 42;
});
}
}
// Comparison example
public class ComparisonDemo
{
public void CompareThreadAndTask()
{
// Thread approach - manual management
var threads = new List();
for (int i = 0; i < 10; i++)
{
var thread = new Thread(() => DoWork());
threads.Add(thread);
thread.Start();
}
foreach (var thread in threads)
{
thread.Join(); // Wait for completion
}
// Task approach - automatic management
var tasks = new List();
for (int i = 0; i < 10; i++)
{
tasks.Add(Task.Run(() => DoWork()));
}
Task.WaitAll(tasks.ToArray()); // Wait for completion
}
private void DoWork()
{
Thread.Sleep(500);
}
}
// Task with proper async/await pattern
public class AsyncPatternExample
{
public async Task FetchDataAsync()
{
// This doesn't block the calling thread
await Task.Delay(1000);
return "Data fetched";
}
public async Task ProcessDataAsync()
{
Console.WriteLine("Start processing");
// Multiple async operations
var task1 = FetchDataAsync();
var task2 = FetchDataAsync();
var task3 = FetchDataAsync();
// Wait for all to complete
string[] results = await Task.WhenAll(task1, task2, task3);
Console.WriteLine($"Processed {results.Length} items");
}
}
Key Differences:
| Aspect | Thread | Task |
|---|---|---|
| Level | Low-level OS construct | High-level abstraction |
| Resource Usage | Heavy (1 MB stack per thread) | Lightweight |
| Pooling | No built-in pooling | Uses ThreadPool |
| Return Value | Cannot return values easily | Can return values via Task<T> |
| Exception Handling | Complex | Integrated with async/await |
| Cancellation | Manual implementation | Built-in via CancellationToken |
| Composability | Difficult | Easy with Task.WhenAll, WhenAny |
| Use Case | Low-level threading control | Most async operations |
Related Resources
Task.Run() is the simpler, modern method for starting a task. It's the recommended approach for most scenarios.
Task.Factory.StartNew() provides more control and configuration options but is more complex and has some gotchas.
Example:
public class TaskCreationComparison
{
// Task.Run() - Simple and recommended
public async Task RunWithTaskRunAsync()
{
// Task.Run() always uses TaskScheduler.Default (ThreadPool)
var result = await Task.Run(() =>
{
Console.WriteLine($"Task.Run on thread: {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(1000);
return 42;
});
Console.WriteLine($"Result: {result}");
}
// Task.Run() with async lambda
public async Task RunWithAsyncLambdaAsync()
{
// Task.Run properly unwraps async lambdas
var result = await Task.Run(async () =>
{
await Task.Delay(1000);
return "Completed";
});
Console.WriteLine(result);
}
// Task.Factory.StartNew() - More control but complex
public async Task RunWithFactoryStartNewAsync()
{
// Requires explicit unwrapping for async lambdas
var task = Task.Factory.StartNew(() =>
{
Console.WriteLine($"Factory.StartNew on thread: {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(1000);
return 42;
});
var result = await task;
Console.WriteLine($"Result: {result}");
}
// Task.Factory.StartNew() with options
public async Task RunWithOptionsAsync()
{
var task = Task.Factory.StartNew(
() =>
{
Console.WriteLine("Long-running task started");
Thread.Sleep(5000);
return "Done";
},
CancellationToken.None,
TaskCreationOptions.LongRunning, // Creates dedicated thread
TaskScheduler.Default
);
var result = await task;
Console.WriteLine(result);
}
// Demonstrating the async lambda gotcha with Factory.StartNew
public async Task DemonstrateGotchaAsync()
{
// WRONG: This returns Task<Task>, not Task
Task<Task> outerTask = Task.Factory.StartNew(async () =>
{
await Task.Delay(1000);
return "Result";
});
// Need to unwrap manually
Task innerTask = await outerTask;
string result = await innerTask;
// OR use Unwrap()
var unwrappedTask = Task.Factory.StartNew(async () =>
{
await Task.Delay(1000);
return "Result";
}).Unwrap();
result = await unwrappedTask;
// Task.Run handles this automatically - CORRECT WAY
result = await Task.Run(async () =>
{
await Task.Delay(1000);
return "Result";
});
}
}
// Practical examples showing when to use each
public class PracticalExamples
{
// Use Task.Run for most scenarios
public async Task<List> ProcessDataAsync(List data)
{
// Offload CPU-intensive work to thread pool
return await Task.Run(() =>
{
return data.Select(x => x * x).ToList();
});
}
// Use Task.Factory.StartNew for long-running tasks
public Task StartBackgroundServiceAsync()
{
return Task.Factory.StartNew(
() =>
{
while (true)
{
// Long-running background work
Console.WriteLine("Service running...");
Thread.Sleep(5000);
}
},
TaskCreationOptions.LongRunning // Gets dedicated thread
);
}
// Use Task.Factory.StartNew with custom scheduler
public async Task RunWithCustomSchedulerAsync()
{
var scheduler = new CustomTaskScheduler();
var task = Task.Factory.StartNew(
() =>
{
Console.WriteLine("Running with custom scheduler");
return 100;
},
CancellationToken.None,
TaskCreationOptions.None,
scheduler
);
var result = await task;
Console.WriteLine($"Result: {result}");
}
}
// Custom task scheduler example
public class CustomTaskScheduler : TaskScheduler
{
protected override IEnumerable GetScheduledTasks()
{
return Enumerable.Empty();
}
protected override void QueueTask(Task task)
{
ThreadPool.QueueUserWorkItem(_ => TryExecuteTask(task));
}
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
return TryExecuteTask(task);
}
}
Key Differences:
| Aspect | Task.Run() | Task.Factory.StartNew() |
|---|---|---|
| Simplicity | Simple, recommended | Complex, more options |
| Default Scheduler | ThreadPool (TaskScheduler.Default) | Can specify custom scheduler |
| Async Lambda | Automatically unwraps | Returns Task<Task<T>> - needs Unwrap() |
| Task Options | Limited options | Full TaskCreationOptions |
| Long-Running | Not ideal | Supports LongRunning option |
| When to Use | 99% of scenarios | Custom schedulers, long-running tasks |
Recommendation: Use Task.Run() unless you specifically need the advanced features of Task.Factory.StartNew().
Related Resources
ConfigureAwait(false) tells the awaited task not to capture and resume on the original synchronization context. This improves performance and avoids potential deadlocks in library code.
When to use:
- In library code (not UI code)
- When you don't need to return to the original context
- To improve performance
- To avoid deadlocks
Example:
// Understanding SynchronizationContext
public class SynchronizationContextExample
{
// WITHOUT ConfigureAwait(false) - captures context
public async Task GetDataWithContextAsync()
{
Console.WriteLine($"Before await - Thread: {Thread.CurrentThread.ManagedThreadId}");
// By default, await captures the current SynchronizationContext
await Task.Delay(1000);
// Resumes on the same context (same thread in UI apps)
Console.WriteLine($"After await - Thread: {Thread.CurrentThread.ManagedThreadId}");
return "Data with context";
}
// WITH ConfigureAwait(false) - doesn't capture context
public async Task GetDataWithoutContextAsync()
{
Console.WriteLine($"Before await - Thread: {Thread.CurrentThread.ManagedThreadId}");
// ConfigureAwait(false) tells it not to capture context
await Task.Delay(1000).ConfigureAwait(false);
// Can resume on any thread pool thread
Console.WriteLine($"After await - Thread: {Thread.CurrentThread.ManagedThreadId}");
return "Data without context";
}
}
// Library code example - SHOULD use ConfigureAwait(false)
public class DataLibrary
{
private readonly HttpClient httpClient = new HttpClient();
// Good: Library code using ConfigureAwait(false)
public async Task FetchDataAsync(string url)
{
// Library code doesn't need UI context
var response = await httpClient.GetAsync(url).ConfigureAwait(false);
// Still on thread pool thread (not UI thread)
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
// Process data without needing UI thread
var processedData = ProcessData(content);
return processedData;
}
public async Task<List> FetchMultipleAsync(List urls)
{
var results = new List();
foreach (var url in urls)
{
// Each await uses ConfigureAwait(false)
var data = await FetchDataAsync(url).ConfigureAwait(false);
results.Add(data);
}
return results;
}
private string ProcessData(string data)
{
// CPU-bound processing
return data.ToUpper();
}
}
// UI/Application code example - DON'T use ConfigureAwait(false)
public class UserInterfaceCode
{
// Bad: Don't use ConfigureAwait(false) in UI code
public async Task UpdateUIAsync()
{
var library = new DataLibrary();
// Get data (library uses ConfigureAwait(false) internally)
var data = await library.FetchDataAsync("https://api.example.com/data");
// We're back on UI thread here, can update UI safely
// Don't use ConfigureAwait(false) here!
UpdateTextBox(data);
}
private void UpdateTextBox(string text)
{
// This needs to run on UI thread
Console.WriteLine($"Updating UI with: {text}");
}
}
// Deadlock prevention example
public class DeadlockExample
{
// This can cause a deadlock in UI apps
public string GetDataSync()
{
// .Result blocks and waits for task
// But task tries to resume on UI thread which is blocked
// DEADLOCK!
return GetDataAsync().Result;
}
private async Task GetDataAsync()
{
await Task.Delay(1000); // Tries to resume on UI thread
return "Data";
}
// Fix with ConfigureAwait(false)
public string GetDataSyncFixed()
{
return GetDataAsyncFixed().Result; // Still not ideal, but won't deadlock
}
private async Task GetDataAsyncFixed()
{
await Task.Delay(1000).ConfigureAwait(false); // Won't try to resume on UI thread
return "Data";
}
}
// Complete example showing best practices
public class BestPracticesExample
{
// Library/Service layer - use ConfigureAwait(false)
public class OrderService
{
public async Task GetOrderAsync(int orderId)
{
await Task.Delay(100).ConfigureAwait(false);
var order = await FetchFromDatabaseAsync(orderId).ConfigureAwait(false);
var details = await FetchOrderDetailsAsync(orderId).ConfigureAwait(false);
order.Details = details;
return order;
}
private async Task FetchFromDatabaseAsync(int id)
{
await Task.Delay(50).ConfigureAwait(false);
return new Order { Id = id, CustomerName = "John Doe" };
}
private async Task<List> FetchOrderDetailsAsync(int orderId)
{
await Task.Delay(50).ConfigureAwait(false);
return new List
{
new OrderDetail { ProductName = "Widget", Quantity = 2 }
};
}
}
// UI/Controller layer - DON'T use ConfigureAwait(false)
public class OrderController
{
private readonly OrderService orderService = new OrderService();
public async Task DisplayOrderAsync(int orderId)
{
// No ConfigureAwait(false) here - we need UI context
var order = await orderService.GetOrderAsync(orderId);
// Can safely update UI because we're on UI thread
Console.WriteLine($"Order for: {order.CustomerName}");
Console.WriteLine($"Items: {order.Details.Count}");
}
}
public class Order
{
public int Id { get; set; }
public string CustomerName { get; set; }
public List Details { get; set; }
}
public class OrderDetail
{
public string ProductName { get; set; }
public int Quantity { get; set; }
}
}
Guidelines:
-
Use
ConfigureAwait(false)in:- Library/framework code
- Service layer methods
- Any code that doesn't need to return to the original context
-
DON'T use
ConfigureAwait(false)in:- UI event handlers
- ASP.NET Core controllers (post .NET Core 2.0+ - no sync context anyway)
- Code that needs to update UI elements
- Top-level application code
Related Resources
A deadlock occurs when two or more operations are waiting for each other to complete, causing the application to freeze indefinitely.
Common async/await deadlock scenario: Blocking on async code (using .Result or .Wait()) in a context with a synchronization context (like UI applications).
Example:
// DEADLOCK EXAMPLES
// Example 1: Classic UI Deadlock
public class UIDeadlockExample
{
// THIS CAUSES A DEADLOCK IN UI APPLICATIONS
public void ButtonClick()
{
// UI thread blocks here waiting for result
var result = GetDataAsync().Result;
// Never reaches here - DEADLOCK!
Console.WriteLine(result);
}
private async Task GetDataAsync()
{
// Simulates async operation (HTTP call, database query, etc.)
await Task.Delay(1000);
// Tries to resume on UI thread, but UI thread is blocked waiting!
// DEADLOCK!
return "Data";
}
// Explanation:
// 1. UI thread calls GetDataAsync().Result and blocks
// 2. GetDataAsync starts and awaits Task.Delay
// 3. When Task.Delay completes, it tries to resume on UI thread
// 4. But UI thread is blocked waiting for the result
// 5. DEADLOCK - each is waiting for the other
}
// Example 2: ASP.NET Deadlock (pre-.NET Core)
public class AspNetDeadlockExample
{
// THIS CAUSES A DEADLOCK IN ASP.NET (not ASP.NET Core)
public ActionResult Index()
{
// ASP.NET request thread blocks here
var data = GetDataAsync().Result;
return View(data);
}
private async Task GetDataAsync()
{
await Task.Delay(1000);
// Tries to resume on ASP.NET synchronization context
return "Data";
}
}
// SOLUTIONS TO DEADLOCKS
// Solution 1: Use async all the way (BEST)
public class AsyncAllTheWayExample
{
// Proper async implementation
public async Task ButtonClickAsync()
{
// Don't block - await instead
var result = await GetDataAsync();
Console.WriteLine(result);
}
private async Task GetDataAsync()
{
await Task.Delay(1000);
return "Data";
}
}
// Solution 2: Use ConfigureAwait(false) in library code
public class ConfigureAwaitSolution
{
public void ButtonClick()
{
// Still not ideal, but won't deadlock
var result = GetDataAsync().Result;
Console.WriteLine(result);
}
private async Task GetDataAsync()
{
// ConfigureAwait(false) prevents capturing sync context
await Task.Delay(1000).ConfigureAwait(false);
// Won't try to resume on UI thread
return "Data";
}
}
// Solution 3: Run on thread pool
public class ThreadPoolSolution
{
public void ButtonClick()
{
// Move work to thread pool
Task.Run(async () =>
{
var result = await GetDataAsync();
// Need to marshal back to UI thread for UI updates
Application.Current.Dispatcher.Invoke(() =>
{
Console.WriteLine(result);
});
});
}
private async Task GetDataAsync()
{
await Task.Delay(1000);
return "Data";
}
}
// COMPLEX DEADLOCK SCENARIOS
// Scenario 1: Nested async calls
public class NestedDeadlockExample
{
public void ProcessData()
{
// Blocking on first async method
var result = FirstMethodAsync().Result;
}
private async Task FirstMethodAsync()
{
// This method awaits another async method
var data = await SecondMethodAsync();
return data.ToUpper();
}
private async Task SecondMethodAsync()
{
await Task.Delay(1000);
// Deadlock occurs here trying to resume
return "data";
}
}
// Scenario 2: Multiple blocking calls
public class MultipleBlockingExample
{
public void ProcessMultiple()
{
// Each of these can cause a deadlock
var result1 = GetData1Async().Result;
var result2 = GetData2Async().Result;
var result3 = GetData3Async().Result;
}
private async Task GetData1Async()
{
await Task.Delay(100);
return "Data1";
}
private async Task GetData2Async()
{
await Task.Delay(100);
return "Data2";
}
private async Task GetData3Async()
{
await Task.Delay(100);
return "Data3";
}
}
// CORRECT PATTERNS
// Pattern 1: Async all the way up
public class CorrectAsyncPattern
{
// Controller/UI method is async
public async Task ProcessDataAsync()
{
var result1 = await GetData1Async();
var result2 = await GetData2Async();
var result3 = await GetData3Async();
Console.WriteLine($"{result1}, {result2}, {result3}");
}
private async Task GetData1Async()
{
await Task.Delay(100);
return "Data1";
}
private async Task GetData2Async()
{
await Task.Delay(100);
return "Data2";
}
private async Task GetData3Async()
{
await Task.Delay(100);
return "Data3";
}
}
// Pattern 2: Library code with ConfigureAwait
public class LibraryCodePattern
{
public async Task GetUserDataAsync(int userId)
{
// All awaits use ConfigureAwait(false)
var user = await FetchUserAsync(userId).ConfigureAwait(false);
var orders = await FetchOrdersAsync(userId).ConfigureAwait(false);
var preferences = await FetchPreferencesAsync(userId).ConfigureAwait(false);
return new UserData
{
User = user,
Orders = orders,
Preferences = preferences
};
}
private async Task FetchUserAsync(int id)
{
await Task.Delay(100).ConfigureAwait(false);
return new User { Id = id, Name = "John" };
}
private async Task<List> FetchOrdersAsync(int userId)
{
await Task.Delay(100).ConfigureAwait(false);
return new List();
}
private async Task FetchPreferencesAsync(int userId)
{
await Task.Delay(100).ConfigureAwait(false);
return new Preferences();
}
public class User { public int Id { get; set; } public string Name { get; set; } }
public class Order { }
public class Preferences { }
public class UserData
{
public User User { get; set; }
public List Orders { get; set; }
public Preferences Preferences { get; set; }
}
}
// Detecting deadlocks
public class DeadlockDetection
{
public void DetectDeadlock()
{
try
{
// Set a timeout to detect potential deadlock
var task = GetDataAsync();
if (!task.Wait(TimeSpan.FromSeconds(5)))
{
Console.WriteLine("Potential deadlock detected!");
}
}
catch (AggregateException ex)
{
Console.WriteLine($"Error: {ex.InnerException?.Message}");
}
}
private async Task GetDataAsync()
{
await Task.Delay(1000);
return "Data";
}
}
Key Takeaways:
- Never block on async code - Don't use
.Resultor.Wait()in UI or ASP.NET contexts - Async all the way - Make your methods async from top to bottom
- Use ConfigureAwait(false) in library code
- ASP.NET Core is safer - It doesn't have a synchronization context, reducing deadlock risk
- Be careful with Task.WaitAll() - Same issues as
.Result