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
248: Common C# Interview Questions on Threading and Async
If you're heading into a C# interview, you're almost certainly going to get grilled on async and await. Most candidates can recite the syntax, but few actually understand what's happening under the hood. I've sat on both sides of the table, and the quickest way to spot someone who doesn't actually "get" threading is when they fall for the most common myth in the .NET ecosystem.
"Async always runs on a background thread" is a lie
I see this constantly. A developer tells me that adding async to a method signature automatically pushes the work onto a different thread to keep the UI responsive. That is fundamentally wrong. async and await are about concurrency, not necessarily parallelism.
Look at this snippet:
public async Task<string> GetWelcomeMessageAsync()
{
return "Hello there!";
}
In this case, no new thread is ever created. The method runs synchronously on the calling thread because there is no actual asynchronous operation to await. Even when you do use await, like awaiting a network call via HttpClient, there isn't necessarily a thread "sitting there" waiting for the bytes to come back from the server. Instead, the runtime uses an I/O completion port. The thread is returned to the pool to do other work, and when the network card signals the OS that the data has arrived, the runtime schedules the remainder of your method to resume.
The takeaway? async is about efficiency—doing more with fewer threads—not just throwing more threads at a problem.
Stop using Task.Run to "fix" synchronous methods
Interviewer: "How do you make a slow, CPU-bound method asynchronous?"
Bad Answer: "I just wrap it in Task.Run(() => SlowMethod()) and await it."
I can't tell you how many times I've seen this. This is called "Async over Sync," and it's an anti-pattern. All you're doing is stealing a thread from the ThreadPool to block it while another thread waits for it. You haven't made the code more scalable; you've actually added the overhead of a context switch for no gain. If a method is CPU-bound, it's synchronous. Period. The Task.Run should happen at the caller level if the caller specifically needs to offload work to keep a UI thread free, not inside the library method itself.
Why ConfigureAwait(false) is the "Senior" Answer
You'll likely be asked about ConfigureAwait(false). In modern .NET (Core/5/6+), it's less of a deal-breaker than it was in the old .NET Framework days, but it's still a critical concept for library authors. By default, when you await something, .NET tries to marshal the continuation back to the original SynchronizationContext (like the UI thread in WPF or the request context in legacy ASP.NET).
If you're writing a generic utility library that calculates hashes or parses JSON, you don't care about the UI thread. By using await someTask.ConfigureAwait(false);, you're telling the runtime: "I don't need to go back to the original thread; just finish this on whatever thread is available." This prevents potential deadlocks and slightly boosts performance by avoiding unnecessary context switches.
The "Async Void" Landmine
If you tell an interviewer that async void is just a shorthand for async Task, the interview is basically over. async void should be used almost exclusively for event handlers.
- You can't await it: The caller has no way to know when the method finished.
- Crash and Burn: If an exception is thrown inside an
async voidmethod, it cannot be caught by a surrounding try-catch block. It will bubble up to theSynchronizationContextand, in many cases, crash the entire process.
Always use async Task if you want the method to be awaitable and exceptions to be handled properly. If you truly want a "fire and forget" pattern, use async Task and explicitly assign the result to a discarded variable: _ = DoSomethingAsync();.
📋 Practical Task
Refactoring the Blocking API Aggregator for True Concurrency
You've inherited a piece of code that is supposed to aggregate data from three different services. However, the current implementation is "fake async"—it blocks threads and will likely deadlock in a UI environment.
Your Task: Refactor the following DataAggregator class. You must:
- Remove the "Async over Sync" pattern (the
Task.Runinside the method). - Ensure the
FetchAllDataAsyncmethod initiates all three requests concurrently rather than sequentially. - Apply
ConfigureAwait(false)to the internal calls to ensure the library doesn't capture the synchronization context.
public class DataAggregator
{
private readonly IApiService _apiService = new ApiService();
// FIX THIS: This is currently sequential and uses "Async over Sync"
public async Task<AggregatedResult> FetchAllDataAsync()
{
// This is a bad pattern: wrapping sync work in Task.Run inside the method
var profile = await Task.Run(() => _apiService.GetProfile());
var posts = await Task.Run(() => _apiService.GetPosts());
var settings = await Task.Run(() => _apiService.GetSettings());
return new AggregatedResult(profile, posts, settings);
}
}
// Assume these are provided and cannot be changed:
public class ApiService
{
public UserProfile GetProfile() { /* Simulates heavy I/O */ return new UserProfile(); }
public List<Post> GetPosts() { /* Simulates heavy I/O */ return new List<Post>(); }
public UserSettings GetSettings() { /* Simulates heavy I/O */ return new UserSettings(); }
}
Hint: Think about Task.WhenAll to achieve true concurrency.
There are no comments for now.