overview
In C#, Thread and Task are both types used for asynchronous operations. Task is a higher level of abstraction than Thread.
System.Threading.Tasks contains types for writing concurrent and asynchronous code.
TaskFactory— static methods for creating and starting tasks.TaskScheduler— thread scheduling infrastructure.Task— a wrapper around a thread that enables easier management.
System.Threading.Tasks.Extensions.ValueTask is a lightweight implementation of a generalized task-returning value.
Task and Task<T> [
Documentation]
Task represents a single asynchronous operation that does not return a value. It is a reference type (so it allocates an object on the heap).
Task.CompletedTaskholds a task that is already completed. If you implement a class that must return aTask(equivalent tovoidin a synchronous method), returnTask.CompletedTask.Task<TResult>’sResultproperty is of typeTResultand holds the value the task returns.- It is a blocking property: accessing it before the task is finished results in blocking the currently active thread until it finishes.
- Access the value by using
awaitinstead.
task lifecycle
A task’s lifecycle is represented by the [TaskStatus] enumeration and contains these states:
- (
0)Created— initialized, but not yet scheduled. - (
1)WaitingForActivation— waiting to be activated and scheduled internally. - (
2)WaitingToRun— scheduled, but not yet started. - (
3)Running— started, but not yet completed. - (
4)WaitingForChildrenToComplete— completed, but waiting for attached child tasks to complete. - (
5)RanToCompletion— completed. - (
6)Canceled— either:- The task has acknowledged cancellation by throwing an
OperationCanceledExceptionwith its ownCancellationToken, or; - The task’s
CancellationTokenwas signaled before the task started.
- The task has acknowledged cancellation by throwing an
- (
7)Faulted— the task completed due to an unhandled exception.
creating and starting
- Tasks that are created with public
Taskconstructors are cold tasks — they are in theCreatedstate. They must beStart()ed. - All tasks that are returned from TAP methods must be started.
- If, internally, a TAP method uses a task’s constructor to instantiate the task to be returned, the TAP method must still call
Starton theTaskobject returned.
- If, internally, a TAP method uses a task’s constructor to instantiate the task to be returned, the TAP method must still call
public static async Task Main()
{
await Task.Run( () )=>
{
…
}
}
or, equivalently using a TaskFactory.StartNew, but this method is no longer recommended.
Task t1 = new(MethodA);— instantiated, but not started.t1.Start()— start the task then continue executing.Caution : callingStarton an active task throws anInvalidOperationException. CheckTask.TaskStatusto determine its state.
Task t2 = Task.Factory.StartNew(MethodB);— instantiated and started.Task t3 = Task.Run(MethodC);— instantiated and started.
waiting
waiting decision table
| Goal | ||
|---|---|---|
| Retrieve the result of a background task | await | Task.Wait or Task.Result |
| Waiting for any task to complete | await Task.WhenAny | Task.WaitAny |
| Waiting for all tasks to complete | await Task.WhenAll | Task.WaitAll |
| Waiting for a period of time | await Task.Delay | Thread.Sleep |
Task.Wait (blocking)
Wait to block the thread that started the Task. Otherwise, if the calling thread is the Main app thread, the app may terminate before the Task completes:
t1.Wait();
// or:
t1.Wait(n); // Wait for n milliseconds.
// or:
t1.Wait(TimeSpan)
// or:
t1.Wait(CancellationToken) // If a CancellationToken is fulfilled while waiting, OperationCanceledException is thrown.
// or:
t1.Wait(n, CancellationToken)
t4.RunSynchronously(); // Start the task on the main thread.
t4.Wait(); // Should still wait in case an error is thrown.
Task.WaitAny (blocking)
To wait on the first of a series of Tasks to finish:
Task.WaitAny(t1, t2, …) // WaitAny() returns the index (an Int32) of which Task in the list finished.
To wait on all the Tasks in a series to finish:
Task.WaitAll(t1, t2, …)
await Task.WhenAll (non-blocking)
public async Task<User> GetUserAsync(int userId)
{
// Given a user Id {userId}, retrieves a User object corresponding
// to the entry in the database with {userId} as its Id.
}
public static async Task<IEnumerable<User>> GetUsersAsync(IEnumerable<int> userIds)
{
var getUserTasks = new List<Task<User>>();
foreach (int userId in userIds)
getUserTasks.Add(GetUserAsync(userId));
return await Task.WhenAll(getUserTasks);
}
await Task.WhenAny (non-blocking)
This method accepts Task.Delay as a parameter to implement a timeout:
Task<Bitmap> download = GetBitmapAsync(url);
if (download == await Task.WhenAny(download, Task.Delay(3000)))
{
// Downloaded the bitmap successfully
}
else
{
// Timed out
}
await Task.WhenEach (non-blocking)
Iterate through tasks as they complete. This avoids the need to repeatedly call Task.WhenAny:
using HttpClient http = new();
Task<string> dotnet = http.GetStringAsync("http://dot.net");
Task<string> bing = http.GetStringAsync("http://www.bing.com");
Task<string> ms = http.GetStringAsync("http://microsoft.com");
await foreach (Task<string> t in Task.WhenEach(bing, dotnet, ms))
{
Console.WriteLine(t.Result);
}
exceptions while waiting
While waiting on one or more Tasks to complete, exceptions thrown are propagated to the thread that called the Wait method:
try
{
Task.WaitAll(t1, t2, …);
}
catch (AggregateException ae)
{
// One or more exceptions were thrown:
foreach (var ex in ae.InnerExceptions)
{
// ex.GetType().Name, ex.Message
}
}