.NET 任务 Task、Task.Run()、 Task.WhenAll()、Task.WhenAny()

文章目录


前言

转载: 方程式sunny

视频教程: 跟着sunny老师学C#

源码: gitee仓库


什么是 Task?


C#中,Task 是一个表示异步操作的对象。简单来说,Task 让你能够在后台运行代码,而不需要等待这个操作完成,从而保持程序的响应性。


想象你在一家餐厅点了一道菜。当你点完后,服务员将你的订单传给厨房,然后你可以继续聊天或看手机,而不需要一直等待菜上桌。这个过程就类似于使用 Task:你发出请求(执行任务),然后继续做其他事情(继续执行程序)。


为什么要使用 Task?

使用 Task 的主要原因是为了提高程序的性能和响应性。特别是在处理耗时的操作(例如网络请求、文件读写等)时,使用 Task 可以:

  • 提升用户体验:用户界面不会因为等待操作而卡顿。
  • 提高资源利用率:能够同时执行多个任务,从而更好地利用系统资源。

如何使用 Task?


(1)使用 Task.Run()

这是最常见的方式,适合简单的异步操作:

csharp 复制代码
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

使用 asyncawait 关键字,使得异步代码更加简洁:

csharp 复制代码
async Task MainAsync()
{
    await Task.Run(() => {
        // 模拟耗时操作
        for (int i = 0; i < 5; i++)
        {
            Console.WriteLine($"Task running: {i}");
        }
    });
​
    Console.WriteLine("Task completed.");
}

在什么地方使用 Task?

Task 特别适合以下场景:

  1. I/O 操作:如读取文件、网络请求等。
  2. 长时间运行的计算:如复杂的数据处理。
  3. 多任务并行处理:如同时处理多个用户请求。

使用优缺点

优点:

  1. 简化异步编程:Task 提供了更高层次的抽象,简化了异步编程的复杂性。
  2. 错误处理更简单:通过AggregateException 处理多个异常。
  3. 可取消:使用 CancellationToken 可以轻松取消任务。

缺点:

  1. 资源管理:过多的并发任务可能会导致资源竞争和性能下降。
  2. 调试难度:异步代码可能会增加调试的复杂性,特别是错误追踪。

如何从任务中返回值

C# 中,使用 Task<T> 可以轻松实现异步方法的返回值。下面重点介绍几种常见的方法来从任务中返回值,并在代码注释中提供解析。


使用 async/await 返回值

csharp 复制代码
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() 返回值

csharp 复制代码
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(任务续接)

csharp 复制代码
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

csharp 复制代码
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> 和相关的方法,可以方便地从异步任务中返回值。主要的几种方法包括:

  1. async/await:最常用的方法,简单明了。
  2. Task.Run():适合简单的计算并直接返回结果。
  3. Continuation:用于在任务完成后执行后续操作。
  4. TaskCompletionSource:手动控制任务的完成,适用于更复杂的场景。

如何执行多个任务

Task.WhenAll()
Task.WhenAny()


为什么需要多个任务?

在实际开发中,特别是涉及 耗时操作 或 并行任务 的情况,通常会遇到以下问题:

  • (1)等待操作完成: 比如进行文件 I/O、网络请求等操作时,如果按照顺序执行,每一步操作都需要等待前一个操作完成,这会导致程序的响应性差。
  • (2)资源利用率低: 如果操作能够并行进行,可以大大提高程序的效率和资源利用率。

为了提高程序性能和响应性,我们常常需要 并行执行多个任务。通过将任务分配到不同的线程或处理器核心,可以在等待一个任务完成的同时,继续执行其他任务,避免资源闲置。例如:

  • (1)如果你需要从多个服务器获取数据,等待每个请求的响应会浪费时间。通过并行发送请求并等待所有响应,可以节省大量的时间。
  • (2)如果你需要执行多个计算任务,顺序执行会导致时间上的浪费。并行处理可以加快结果的获得。

如何执行多个任务?

C# 提供了多种方式来并行执行多个任务,主要有以下两种方法:

  • (1)Task.WhenAll():等待所有任务完成。
  • (2)Task.WhenAny():等待任意一个任务完成。
    这两种方法可以根据不同的需求选择,接下来我将通过代码示例来讲解每种方法的使用。

使用 Task.WhenAll() 执行多个任务

  • Task.WhenAll()方法用于并行执行多个任务,并且只有当所有任务都完成时,才会继续执行后续代码。
  • 这个方法适用于你需要等待 所有任务 完成后,才能继续做其他事情的场景。

示例:

csharp 复制代码
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() 方法等待多个任务中的任意一个完成,一旦有任务完成,后续代码会立即执行。
  • 这适用于你只关心 最先完成的任务,而不需要等所有任务都完成的场景。

示例:

csharp 复制代码
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() 只等待 第一个完成的任务,无论是 task1task2 还是 task3,只要有一个任务先完成,就会继续执行后续代码。
  • 该方法返回的是 第一个完成的任务,在这里我们打印该任务的 ID
  • 这种方式适用于你只关心最先完成的任务的场景。比如你发起了多个网络请求,但只关心第一个请求的响应。

应用场景:

  • (1)网络请求:例如,向多个服务器请求数据,但你只关心最快返回的一个响应。
  • (2)响应优先:在多个计算任务中,你只关心最早完成的任务,并基于其结果执行后续操作。

总结

为什么需要多个任务?

  • 在处理多个耗时操作时,顺序执行会浪费大量时间。
  • 通过并行执行多个任务,可以显著提高程序的效率和响应速度。

如何执行多个任务?

  • (1)Task.WhenAll():等待所有任务完成后才继续执行。适用于所有任务都必须完成后才能做进一步处理的场景。
  • (2)Task.WhenAny():等待任意一个任务完成。一旦某个任务完成,就立即继续执行后续代码,适用于你只关心最先完成的任务。

应用场景:

  • (1)Task.WhenAll() 适用于需要等所有任务完成后才继续处理的场景(如并行下载多个文件、计算多个数据源的结果等)。
  • (2)Task.WhenAny() 适用于只关心第一个完成的任务的场景(如响应最早的网络请求或最先完成的计算任务)。

相关推荐
技术砖家--Felix4 小时前
Spring Boot数据访问篇:整合MyBatis操作数据库
数据库·spring boot·mybatis
银河技术4 小时前
Redis 限流最佳实践:令牌桶与滑动窗口全流程实现
数据库·redis·缓存
Crazy Struggle4 小时前
一行代码快速开发 AntdUI 风格的 WinForm 通用后台框架
.net·winform·antdui
小白考证进阶中4 小时前
如何拿到Oracle OCP(Oracle 19c)?
数据库·oracle·dba·开闭原则·ocp认证·oracle认证·oracleocp
IAR Systems5 小时前
使用J-Link Attach NXP S32K3导致对应RAM区域被初始化成0xDEADBEEF
arm开发·数据库·嵌入式软件开发·iar
RestCloud5 小时前
OceanBase 分布式数据库的 ETL 实践:从抽取到实时分析
数据库·分布式·postgresql·oceanbase·etl·数据处理·数据同步
BingoGo5 小时前
PHP 中的命名艺术 实用指南
后端·php
码顺5 小时前
记录一次Oracle日志listener.log文件大小超过4G后出现Tomcat服务启动一直报错的原因【ORACLE】
数据库·oracle·tomcat
一只小透明啊啊啊啊6 小时前
SQL 查询语句的子句的执行顺序
数据库·sql