.NET 任务 Task、Task.Run()、 Task.WhenAll()、Task.WhenAny()
文章目录
- 前言
- 什么是 Task?
- 为什么要使用 Task?
- 如何使用 Task?
- (1)使用 Task.Run()
- (2)使用 async/await
- 在什么地方使用 Task?
- 使用优缺点
- 如何从任务中返回值
- 使用 async/await 返回值
- 使用 Task.Run() 返回值
- 使用 Continuation(任务续接)
- 使用 TaskCompletionSource
- 总结
- 如何执行多个任务
- 为什么需要多个任务?
- 如何执行多个任务?
- 使用 Task.WhenAll() 执行多个任务
- 使用 Task.WhenAny() 执行多个任务
- 总结
- 为什么需要多个任务?
- 如何执行多个任务?
- 应用场景:
前言
转载: 方程式sunny
视频教程: 跟着sunny老师学C#
源码: gitee仓库
什么是 Task?
在 C#中,Task 是一个表示异步操作的对象。简单来说,Task 让你能够在后台运行代码,而不需要等待这个操作完成,从而保持程序的响应性。
想象你在一家餐厅点了一道菜。当你点完后,服务员将你的订单传给厨房,然后你可以继续聊天或看手机,而不需要一直等待菜上桌。这个过程就类似于使用 Task:你发出请求(执行任务),然后继续做其他事情(继续执行程序)。
为什么要使用 Task?
使用 Task 的主要原因是为了提高程序的性能和响应性。特别是在处理耗时的操作(例如网络请求、文件读写等)时,使用 Task 可以:
- 提升用户体验:用户界面不会因为等待操作而卡顿。
- 提高资源利用率:能够同时执行多个任务,从而更好地利用系统资源。
如何使用 Task?
(1)使用 Task.Run()
这是最常见的方式,适合简单的异步操作:
using System;
using System.Threading.Tasks;
class Program
{static void Main(){Task task = Task.Run(() => {for (int i = 0; i < 5; i++){Console.WriteLine($"Task running: {i}");}});
task.Wait(); // 等待任务完成Console.WriteLine("Task completed.");}
}
(2)使用 async/await
使用 async 和 await 关键字,使得异步代码更加简洁:
async Task MainAsync()
{await Task.Run(() => {// 模拟耗时操作for (int i = 0; i < 5; i++){Console.WriteLine($"Task running: {i}");}});
Console.WriteLine("Task completed.");
}
在什么地方使用 Task?
Task 特别适合以下场景:
I/O操作:如读取文件、网络请求等。- 长时间运行的计算:如复杂的数据处理。
- 多任务并行处理:如同时处理多个用户请求。
使用优缺点
优点:
- 简化异步编程:
Task提供了更高层次的抽象,简化了异步编程的复杂性。 - 错误处理更简单:通过
AggregateException处理多个异常。 - 可取消:使用
CancellationToken可以轻松取消任务。
缺点:
- 资源管理:过多的并发任务可能会导致资源竞争和性能下降。
- 调试难度:异步代码可能会增加调试的复杂性,特别是错误追踪。
如何从任务中返回值
在 C# 中,使用 Task<T> 可以轻松实现异步方法的返回值。下面重点介绍几种常见的方法来从任务中返回值,并在代码注释中提供解析。
使用 async/await 返回值
using System;
using System.Threading.Tasks;
class Program
{static async Task Main(string[] args){// 调用异步方法并等待其返回结果int result = await CalculateSumAsync(5, 10);Console.WriteLine($"计算结果: {result}");}
static async Task<int> CalculateSumAsync(int a, int b){// 模拟一个耗时的异步操作await Task.Delay(1000);return a + b; // 返回计算结果}
}
使用 Task.Run() 返回值
using System;
using System.Threading.Tasks;
class Program
{static void Main(){// 使用 Task.Run 启动一个新任务来执行计算Task<int> task = Task.Run(() => {return Calculate(5, 10); // 执行计算并返回结果});
// 等待任务完成并获取结果int result = task.Result; Console.WriteLine($"计算结果: {result}");}
static int Calculate(int a, int b){return a + b; // 返回计算结果}
}
使用 Continuation(任务续接)
using System;
using System.Threading.Tasks;
class Program
{static void Main(){// 启动任务来执行计算Task<int> task = Task.Run(() => Calculate(5, 10));// 使用 ContinueWith 在任务完成后处理结果task.ContinueWith(t => {Console.WriteLine($"计算结果: {t.Result}"); // 获取原任务的返回值});// 等待用户输入,以便查看结果Console.ReadLine();}
static int Calculate(int a, int b){return a + b; // 返回计算结果}
}
使用 TaskCompletionSource
using System;
using System.Threading.Tasks;
class Program
{static void Main(){// 创建 TaskCompletionSource 来手动控制任务的完成var tcs = new TaskCompletionSource<int>();
Task.Run(() =>{// 模拟耗时操作Task.Delay(1000).Wait();tcs.SetResult(42); // 设置任务的结果});
// 获取任务的结果,等待并获取结果int result = tcs.Task.Result; Console.WriteLine($"计算结果: {result}");}
}
总结
在 C# 中,通过 Task<T> 和相关的方法,可以方便地从异步任务中返回值。主要的几种方法包括:
async/await:最常用的方法,简单明了。Task.Run():适合简单的计算并直接返回结果。Continuation:用于在任务完成后执行后续操作。TaskCompletionSource:手动控制任务的完成,适用于更复杂的场景。
如何执行多个任务
Task.WhenAll()
Task.WhenAny()
为什么需要多个任务?
在实际开发中,特别是涉及 耗时操作 或 并行任务 的情况,通常会遇到以下问题:
- (1)等待操作完成: 比如进行文件
I/O、网络请求等操作时,如果按照顺序执行,每一步操作都需要等待前一个操作完成,这会导致程序的响应性差。 - (2)资源利用率低: 如果操作能够并行进行,可以大大提高程序的效率和资源利用率。
为了提高程序性能和响应性,我们常常需要 并行执行多个任务。通过将任务分配到不同的线程或处理器核心,可以在等待一个任务完成的同时,继续执行其他任务,避免资源闲置。例如:
- (1)如果你需要从多个服务器获取数据,等待每个请求的响应会浪费时间。通过并行发送请求并等待所有响应,可以节省大量的时间。
- (2)如果你需要执行多个计算任务,顺序执行会导致时间上的浪费。并行处理可以加快结果的获得。
如何执行多个任务?
C# 提供了多种方式来并行执行多个任务,主要有以下两种方法:
- (1)
Task.WhenAll():等待所有任务完成。 - (2)
Task.WhenAny():等待任意一个任务完成。
这两种方法可以根据不同的需求选择,接下来我将通过代码示例来讲解每种方法的使用。
使用 Task.WhenAll() 执行多个任务
Task.WhenAll()方法用于并行执行多个任务,并且只有当所有任务都完成时,才会继续执行后续代码。- 这个方法适用于你需要等待 所有任务 完成后,才能继续做其他事情的场景。
示例:
using System;
using System.Threading.Tasks;
class Program
{static async Task Main(string[] args){// 启动多个异步任务(这里模拟的是3个耗时的任务)Task task1 = Task.Run(() => DoWork(1)); // 第一个任务Task task2 = Task.Run(() => DoWork(2)); // 第二个任务Task task3 = Task.Run(() => DoWork(3)); // 第三个任务
// Task.WhenAll() 等待所有任务完成,只有所有任务都完成后才会继续执行await Task.WhenAll(task1, task2, task3);
// 当所有任务都完成时输出消息Console.WriteLine("所有任务完成!");}
static void DoWork(int taskId){// 模拟每个任务的耗时操作(例如网络请求、计算任务等)Task.Delay(1000).Wait(); // 等待1秒Console.WriteLine($"任务 {taskId} 完成"); // 打印任务完成信息}
}
解释:
- 在这个例子中,我们通过
Task.Run()启动了 3 个异步任务,模拟了执行耗时操作(如计算、I/O操作等)。 Task.WhenAll()等待所有任务完成。只有当所有任务都完成后,控制台才会输出 “所有任务完成!”。- 这种方式适用于 所有任务都必须完成后才需要执行的场景。
- 例如,等待多个文件下载完毕后再处理它们,或者等多个数据源都准备好数据后再开始处理。
应用场景:
- (1) 网络请求:例如,你同时向多个 API 发起请求,并在所有请求返回后进行处理。
- (2) 数据处理:同时进行多个计算任务,等待所有计算完成后汇总结果。
使用 Task.WhenAny() 执行多个任务
Task.WhenAny()方法等待多个任务中的任意一个完成,一旦有任务完成,后续代码会立即执行。- 这适用于你只关心 最先完成的任务,而不需要等所有任务都完成的场景。
示例:
using System;
using System.Threading.Tasks;
class Program
{static async Task Main(string[] args){// 启动多个异步任务(这里模拟的是3个耗时的任务)Task task1 = Task.Run(() => DoWork(1)); // 第一个任务Task task2 = Task.Run(() => DoWork(2)); // 第二个任务Task task3 = Task.Run(() => DoWork(3)); // 第三个任务
// Task.WhenAny() 等待任意一个任务完成,并且一旦有任务完成就继续执行Task firstCompletedTask = await Task.WhenAny(task1, task2, task3);
// 输出第一个完成任务的信息Console.WriteLine($"至少有一个任务完成!任务编号:{firstCompletedTask.Id}");}
static void DoWork(int taskId){// 模拟每个任务的耗时操作(例如网络请求、计算任务等)Task.Delay(1000).Wait(); // 等待1秒Console.WriteLine($"任务 {taskId} 完成"); // 打印任务完成信息}
}
解析:
Task.WhenAny()只等待 第一个完成的任务,无论是task1、task2还是task3,只要有一个任务先完成,就会继续执行后续代码。- 该方法返回的是 第一个完成的任务,在这里我们打印该任务的
ID。 - 这种方式适用于你只关心最先完成的任务的场景。比如你发起了多个网络请求,但只关心第一个请求的响应。
应用场景:
- (1)网络请求:例如,向多个服务器请求数据,但你只关心最快返回的一个响应。
- (2)响应优先:在多个计算任务中,你只关心最早完成的任务,并基于其结果执行后续操作。
总结
为什么需要多个任务?
- 在处理多个耗时操作时,顺序执行会浪费大量时间。
- 通过并行执行多个任务,可以显著提高程序的效率和响应速度。
如何执行多个任务?
- (1)
Task.WhenAll():等待所有任务完成后才继续执行。适用于所有任务都必须完成后才能做进一步处理的场景。 - (2)
Task.WhenAny():等待任意一个任务完成。一旦某个任务完成,就立即继续执行后续代码,适用于你只关心最先完成的任务。
应用场景:
- (1)
Task.WhenAll()适用于需要等所有任务完成后才继续处理的场景(如并行下载多个文件、计算多个数据源的结果等)。 - (2)
Task.WhenAny()适用于只关心第一个完成的任务的场景(如响应最早的网络请求或最先完成的计算任务)。
