1. What is the difference between Task and ValueTask?
Both Task and ValueTask represent asynchronous operations in C#.
The main difference is memory allocation and performance.
|
Task |
ValueTask |
|---|---|
|
Reference type ( |
Value type ( |
|
Typically involves a Task object |
Can avoid Task allocation |
|
Easy to use |
Requires careful handling |
|
Can be awaited multiple times |
Generally should be awaited only once |
|
Recommended for most async methods |
Useful for frequently synchronous results |
Example
// Using Task
public async Task<int> GetNumberAsync()
{
await Task.Delay(1000);
return 100;
}
// Using ValueTask
public ValueTask<int> GetNumberAsync()
{
return ValueTask.FromResult(100);
}
Explanation:
-
Task<int>represents an asynchronous operation returning an integer. -
ValueTask<int>can return an integer immediately without allocating a new Task object. -
ValueTaskis useful when the result is frequently available synchronously, such as from a cache.
Key Points
-
Prefer
Taskfor normal asynchronous programming. -
Use
ValueTaskwhen performance measurements justify it. -
ValueTaskis not automatically faster thanTask. -
Avoid awaiting the same
ValueTaskmultiple times unless its source explicitly supports it.
2. What are asynchronous streams using IAsyncEnumerable<T>?
IAsyncEnumerable<T> allows us to retrieve and process data asynchronously, one item at a time, instead of waiting for the entire collection.
It is useful for large datasets, paginated APIs, and streaming data.
Example
public async IAsyncEnumerable<int> GetNumbersAsync()
{
for (int i = 1; i <= 5; i++)
{
await Task.Delay(1000);
yield return i;
}
}
// Calling method
await foreach (int number in GetNumbersAsync())
{
Console.WriteLine(number);
}
Output:
1 (after 1 second)
2 (after 2 seconds)
3 (after 3 seconds)
4 (after 4 seconds)
5 (after 5 seconds)
Explanation:
-
IAsyncEnumerable<int>returns an asynchronous sequence of integers. -
yield returnproduces one value at a time. -
await foreachasynchronously retrieves each value. -
The application does not need to wait for all five values before processing the first.
Flow Diagram
GetNumbersAsync()
|
v
Wait 1 second
|
v
Return 1 ---> Process 1
|
Wait 1 second
|
v
Return 2 ---> Process 2
|
...
|
Return 5 ---> Process 5
Key Points
-
Introduced in C# 8.0.
-
Uses
IAsyncEnumerable<T>,yield return, andawait foreach. -
Suitable for large datasets and streaming.
-
Avoids buffering the entire sequence when items can be processed individually.
-
Supports cancellation using
CancellationToken.
3. How do exceptions propagate in async methods?
When an exception occurs inside an asynchronous method returning Task or Task<T>, the exception is stored in the returned Task.
When the caller uses await, the exception is rethrown and can be handled using try-catch.
Example
public async Task<int> DivideAsync()
{
await Task.Delay(1000);
int a = 10;
int b = 0;
return a / b;
}
// Calling method
try
{
int result = await DivideAsync();
Console.WriteLine(result);
}
catch (DivideByZeroException ex)
{
Console.WriteLine("Error: " + ex.Message);
}
Output:
Error: Attempted to divide by zero.
Explanation:
-
DivideAsync()starts execution. -
Task.Delay()introduces an asynchronous wait. -
Division by zero causes an exception.
-
The returned Task becomes faulted.
-
awaitrethrows the exception. -
The caller's
catchblock handles it.
Flow Diagram
Caller
|
v
await DivideAsync()
|
v
Exception occurs
|
v
Task becomes Faulted
|
v
await rethrows exception
|
v
catch block handles it
Key Points
-
Exceptions from
Task-returning async methods are captured in the Task. -
awaitrethrows the original exception type. -
Use
try-catcharoundawait. -
Task.WhenAll()can store multiple exceptions; a singleawaitgenerally throws one of them. -
async voidexceptions cannot be handled by awaiting the method and may crash the application.
4. What happens when an async method is called without awaiting it?
When an asynchronous method is called without await, the method starts executing, but the caller does not wait for its completion.
This is sometimes called fire-and-forget, although simply discarding a Task does not provide safe background execution.
Example
public async Task SendEmailAsync()
{
await Task.Delay(3000);
Console.WriteLine("Email sent");
}
// Calling method
public async Task ProcessAsync()
{
SendEmailAsync(); // No await
Console.WriteLine("Process completed");
await Task.Delay(4000);
}
Output:
Process completed
Email sent
Explanation:
-
SendEmailAsync()starts executing. -
It reaches
Task.Delay(3000)and returns an incomplete Task. -
Because there is no
await, the caller continues immediately. -
"Process completed"prints first. -
After approximately three seconds,
"Email sent"prints.
The final Task.Delay(4000) keeps this example running long enough to observe the email operation. Without it, the operation might not finish before the application exits.
Correct Approach
public async Task ProcessAsync()
{
await SendEmailAsync();
Console.WriteLine("Process completed");
}
Output:
Email sent
Process completed
Key Points
-
An async method starts executing when called, even without
await. -
The caller continues without waiting for completion.
-
Exceptions may go unobserved.
-
The operation may not complete if the application shuts down.
-
In ASP.NET Core, avoid untracked fire-and-forget operations that depend on scoped services such as
DbContext. -
For reliable background processing, use
BackgroundServiceor a background job queue.
Quick Interview Revision
|
Concept |
Important Point |
|---|---|
|
|
Standard way to represent asynchronous operations |
|
|
Can reduce allocations when results are frequently synchronous |
|
|
Asynchronously processes items one by one |
|
Async exceptions |
Stored in Task and rethrown by |
|
Async without |
Caller continues without waiting; exceptions and completion need explicit handling |
Interview tip: For ASP.NET Core Web API development, prefer Task and async/await for database calls, HTTP requests, and other I/O operations. Use ValueTask only when a measured performance benefit justifies its additional complexity.