C# Task
最后修改于 2023 年 7 月 5 日
在本文中,我们将展示如何在 C# 中使用 Task 进行并发操作。
并发编程用于两种任务:I/O 密集型任务和 CPU 密集型任务。从网络请求数据、访问数据库或读取和写入是 IO 密集型任务。CPU 密集型任务是计算量大的任务,例如数学计算或图形处理。
异步操作适用于 I/O 密集型任务。并行操作适用于 CPU 密集型任务。与其他语言不同,Task 可用于异步和并行操作。
Task
表示一个并发操作。
Task Task<TResult>
Task
表示一个并发操作,而 Task<TResult> 表示一个可以返回值的并发操作。
Task.Run
方法用于并发地(理想情况下是并行地)运行 CPU 密集型代码。它将指定的工作排队以在 ThreadPool 上运行,并返回该工作的 Task 或 Task<TResult> 句柄。
.NET 包含许多方法,例如 StreamReader.ReadLineAsync
或 HttpClient.GetAsync
,它们异步执行 I/O 密集型代码。它们与 async/await
关键字一起使用。
Task.Run
而不是使用 new Task(); task.Start()
。C# Task.Run
Task.Run
方法将任务放在不同的线程上。它适用于 CPU 密集型任务。
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; }
主线程在生成的任务完成之前完成。为了看到任务完成,我们使用 Console.ReadLine
等待用户输入。
$ dotnet run Main thread 1 begin Main thread 1 end Thread 4 begin Thread 4 end
Task<TResult>
表示一个返回结果的任务。
Task<int> task = Task.Run(() => { Thread.Sleep(3000); return 2 + 3; }); var res = await task; Console.WriteLine(res);
该程序展示了如何等待返回计算结果的任务。
C# Task.Delay
Task.Delay
创建一个在一段时间延迟后完成的任务。
Console.WriteLine("step 1"); await doTask(); Console.WriteLine("step 2"); async Task doTask() { await Task.Delay(3000); Console.WriteLine("task finished"); }
创建任务的函数必须使用 async
关键字。
await Task.Delay(3000);
Task.Delay
创建一个新任务,该任务休眠三秒钟。await
运算符等待任务完成。它阻止主程序的执行,直到任务完成。
$ dotnet run step 1 task finished step 2
C# async Main 方法
当我们在 Main
方法中使用 await
运算符时,我们必须使用 async
修饰符标记它。
sky main club cotton rocket
这是一个示例文本文件。
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}"); } }
该示例异步读取文件的第一行。这项工作是在 Main
方法内部完成的。
string? res = await reader.ReadLineAsync();
ReadLineAsync
方法返回一个 Task<String>
,它表示一个异步读取操作。任务中的结果包含来自流的下一行,如果已读取所有字符,则为 null。
$ dotnet run First line is: sky
C# Task.WaitAll
Task.WaitAll
方法等待所有提供的任务完成执行。
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"); }
我们测量了三个异步方法的执行时间。
Task.WaitAll(f1(), f2(), f3());
Task.WaitAll
等待所有提供的任务完成执行。
$ dotnet run f3 finished f1 finished f2 finished elapsed: 7000 ms
C# Task.ContinueWith
Task.ContinueWith
创建一个延续,当目标 Task<TResult>
完成时,该延续异步执行。
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; }
在该示例中,我们使用 ContinueWith
链接两个操作。
C# 多个异步请求
HttpClient
类用于发送 HTTP 请求并从指定的资源接收 HTTP 响应。
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); }
我们向各种网页发送异步 GET 请求,并获取它们的响应状态代码。
tasks.Add(client.GetAsync(url));
GetAsync
向指定的 url 发送一个 GET 请求,并在一个异步操作中返回响应体。 它返回一个新的任务。 该任务被添加到任务列表中。
Task.WaitAll(tasks.ToArray());
Task.WaitAll
等待所有提供的任务完成执行。
data.Add(await task);
await
解包操作的结果。
foreach (var res in data) { Console.WriteLine(res.StatusCode); }
我们打印每个请求的状态。
$ dotnet run OK OK OK OK OK OK
来源
在本文中,我们使用了 Task 在 C# 中进行并发操作。
作者
列出所有 C# 教程。