C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
51: The Task-Based Asynchronous Pattern
I want to show you a piece of code that looks perfectly fine at first glance. I’ve seen this exact pattern in countless PRs from developers who are just starting to dip their toes into asynchronous programming in C#. They know the method they're calling is asynchronous, but they're calling it from a synchronous context and just want the value now.
public class UserProfileService
{
private readonly ApiClient _client = new ApiClient();
public string GetUserBio(int userId)
{
// The method is async, but we just want the string result.
// Why not just use .Result?
var bio = _client.FetchBioAsync(userId).Result;
return bio;
}
}
If you run this in a simple console app, it might actually work. But the moment you move this code into a UI framework (like WPF or WinForms) or an older ASP.NET application, your app will likely freeze. Deadlock. Total silence.
The deadlock caused by .Result
Here is what's happening under the hood. When you call .Result (or .Wait()), you are telling the current thread to stop everything and block until the Task completes. However, in many .NET environments, there is a "SynchronizationContext" that ensures the code following an await returns to the original thread (like the UI thread).
So, the FetchBioAsync method finishes its work and tries to jump back onto the original thread to wrap up. But that thread is currently blocked, waiting for .Result to finish. The Task is waiting for the thread, and the thread is waiting for the Task. They're staring at each other in a stalemate, and your application stops responding.
Going "Async All the Way Up"
The only real fix for this is to embrace the Task-Based Asynchronous Pattern (TAP) fully. You can't just sprinkle async in one place; it has to permeate your call stack. We change the return type to Task<T> and use the await keyword.
public class UserProfileService
{
private readonly ApiClient _client = new ApiClient();
public async Task<string> GetUserBioAsync(int userId)
{
// No blocking here. The thread is released to do other work
// until the API call actually returns.
var bio = await _client.FetchBioAsync(userId);
return bio;
}
}
By returning a Task<string>, you're telling the caller: "I'm not giving you a string immediately; I'm giving you a promise that I'll eventually have a string for you." The caller then awaits that task, and the chain continues without blocking any threads.
Understanding the Task and Task<T> contract
TAP is built around the Task class. Think of a Task as a handle to an operation that is happening elsewhere. If the method returns Task (non-generic), it's the async equivalent of void—you're interested in when it finishes, but you don't expect a value back.
If it returns Task<T>, it's the async equivalent of T. I always recommend naming these methods with the Async suffix. It's not a compiler requirement, but it's a massive signal to other developers that they need to await the result rather than treating it like a synchronous call.
Handling multiple tasks with Task.WhenAll
One of the biggest advantages of TAP is how easily you can handle concurrency. Imagine you need to fetch data from three different APIs. If you await each one individually, you're running them in sequence, which is a waste of time.
public async Task LoadDashboardDataAsync()
{
// Start all three tasks immediately without awaiting them yet
Task<User> userTask = _userService.GetUserInfoAsync();
Task<List<Order>> ordersTask = _orderService.GetRecentOrdersAsync();
Task<Weather> weatherTask = _weatherService.GetLocalWeatherAsync();
// Now, wait for ALL of them to finish concurrently
await Task.WhenAll(userTask, ordersTask, weatherTask);
// Now we can safely access the .Result because we know they are finished
var user = userTask.Result;
var orders = ordersTask.Result;
var weather = weatherTask.Result;
UpdateUI(user, orders, weather);
}
Notice that I used .Result at the end. Normally, I'd tell you to avoid that like the plague. But since Task.WhenAll has already completed, the tasks are guaranteed to be finished, and calling .Result there is safe and won't cause a deadlock.
📋 Practical Task
Exercise: Implementing a Concurrent Website Health Checker
You are building a tool that checks the status of multiple URLs to see if they are online. If you check them one by one, it will take forever. Your goal is to use the Task-Based Asynchronous Pattern to check them all at once.
Requirements:
- Create a method
public async Task<List<string>> CheckUrlsAsync(List<string> urls). - Inside this method, create a list of
Task<string>. - For each URL, call a mock method
private async Task<string> PingUrlAsync(string url)(which you should implement to simulate a delay usingawait Task.Delay(500)and return a string like "URL [url] is Up"). - Use
Task.WhenAllto wait for all the pings to complete. - Return a list containing the results of all the completed tasks.
Constraint: Ensure that you are not awaiting the PingUrlAsync call inside the foreach loop, as that would execute the requests sequentially rather than concurrently.
There are no comments for now.