C# Task
last modified July 5, 2023
In this article we show how to use Task for concurrent operations in C#.
Concurrent programming is used for two kinds of tasks: I/O-bound and CPU-boud tasks. Requesting data from a network, accessing a database, or reading and writing are IO-bound tasks. CPU-boud tasks are tasks that are computationally expensive, such as mathematical calculations or graphics processing.
Asynchronous operations are suited for I/O-bound tasks. Parallel operations are suited for CPU-bound tasks. Unlike in other languages, Task can be used for both asynchronous and parallel operations.
Task represents a concurrent operation.
Task Task<TResult>
Task represents an concurrent operation, while Task<TResult>
represents an concurrent operation that can return a value.
The Task.Run method is used to run CPU-bound code concurrently;
ideally in parallel. It queues the specified work to run on the ThreadPool and
returns a task or Task<TResult> handle for that work.
.NET contains numerous methods such as StreamReader.ReadLineAsync
or HttpClient.GetAsync that execute I/O-bound code asynchronously.
They are used together with async/await keywords.
Task.Run
instead of using new Task(); task.Start().
C# Task.Run
The Task.Run method puts a task on a different thread. It is
suitable for CPU-bound tasks.
Console.WriteLine($"Main thread {getThreadId()} begin");
Task.Run(() =>
{
Console.WriteLine($"Thread {getThreadId()} begin");
Thread.Sleep(3000);
Console.WriteLine($"Thread {getThreadId()} end");
});
Console.WriteLine($"Main thread {getThreadId()} end");
Console.ReadLine();
int getThreadId()
{
return Thread.CurrentThread.ManagedThreadId;
}
The main thread finishes before the generated task. In order to see the task
finished, we use Console.ReadLine which waits for user input.
$ dotnet run Main thread 1 begin Main thread 1 end Thread 4 begin Thread 4 end
Task<TResult> represents a task which returns a result.
Task<int> task = Task.Run(() =>
{
Thread.Sleep(3000);
return 2 + 3;
});
var res = await task;
Console.WriteLine(res);
The program shows how to wait for a task that returns a computation result.
C# Task.Delay
Task.Delay creates a task which completes after a time delay.
Console.WriteLine("step 1");
await doTask();
Console.WriteLine("step 2");
async Task doTask()
{
await Task.Delay(3000);
Console.WriteLine("task finished");
}
The function which creates a task must use the async keyword.
await Task.Delay(3000);
Task.Delay creates a new task, which sleeps for three seconds.
The await operator waits for the task to finish. It block execution
of the main program until the task is finished.
$ dotnet run step 1 task finished step 2
C# async Main method
When we are using the await operator inside the Main
method, we have to mark it with the async modifier.
sky main club cotton rocket
This is a sample text file.
namespace AsyncMain;
class Program
{
static async Task Main(string[] args)
{
using StreamReader reader = File.OpenText("words.txt");
string? res = await reader.ReadLineAsync();
Console.WriteLine($"First line is: {res}");
}
}
The example reads the first line of a file asynchronously. The work is done
inside the Main method.
string? res = await reader.ReadLineAsync();
The ReadLineAsync method returns a Task<String>
that represents an asynchronous read operation. The result in a task contains
the next line from the stream, or is null if all the characters have been read.
$ dotnet run First line is: sky
C# Task.WaitAll
The Task.WaitAll method waits for all of the provided tasks to
complete execution.
using System.Diagnostics;
var sw = new Stopwatch();
sw.Start();
Task.WaitAll(f1(), f2(), f3());
sw.Stop();
var elapsed = sw.ElapsedMilliseconds;
Console.WriteLine($"elapsed: {elapsed} ms");
async Task f1()
{
await Task.Delay(4000);
Console.WriteLine("f1 finished");
}
async Task f2()
{
await Task.Delay(7000);
Console.WriteLine("f2 finished");
}
async Task f3()
{
await Task.Delay(2000);
Console.WriteLine("f3 finished");
}
We measure the execution time of three asynchronous methods.
Task.WaitAll(f1(), f2(), f3());
The Task.WaitAll waits for all of the provided tasks to complete
execution.
$ dotnet run f3 finished f1 finished f2 finished elapsed: 7000 ms
C# Task.ContinueWith
The Task.ContinueWith creates a continuation that executes
asynchronously when the target Task<TResult> completes.
Task<int> task = Task.Run(() =>
runTask()).ContinueWith<int>((x) => x.Result * 2);
var res = await task;
Console.WriteLine(res);
int runTask()
{
int x = 1;
int y = 2;
int z = 3;
Thread.Sleep(1000);
return x + y + z;
}
In the example, we chain two operations with ContinueWith.
C# mulitple async requests
The HttpClient class is used for sending HTTP requests and
receiving HTTP responses from the specified resource.
var urls = new string[] { "http://webcode.me", "http://example.com",
"http://httpbin.org", "https://ifconfig.me", "http://termbin.com",
"https://github.com"
};
using var client = new HttpClient();
var tasks = new List<Task<HttpResponseMessage>>();
foreach (var url in urls)
{
tasks.Add(client.GetAsync(url));
}
Task.WaitAll(tasks.ToArray());
var data = new List<HttpResponseMessage>();
foreach (var task in tasks)
{
data.Add(await task);
}
foreach (var res in data)
{
Console.WriteLine(res.StatusCode);
}
We send asynchronous GET requests to various web pages and get their response status codes.
tasks.Add(client.GetAsync(url));
The GetAsync sends a GET request to the specified url and returns
the response body in an asynchronous operation. It returns a new task. The task
is added to the list of tasks.
Task.WaitAll(tasks.ToArray());
The Task.WaitAll waits for all of the provided tasks to complete
execution.
data.Add(await task);
The await unwraps the result of the operation.
foreach (var res in data)
{
Console.WriteLine(res.StatusCode);
}
We print the status of each request.
$ dotnet run OK OK OK OK OK OK
Source
Task class - language reference
In this article we have used Task for concurrent operations in C#.
Author
List all C# tutorials.