一、基本概念

  • 在 C# 中,Task(任务) 属于 System.Threading.Tasks 命名空间中。自 .NET 4.0 引入以来,已成为 C# 异步和并行编程的核心基础功能。
  • Task 就是一个 “待执行的工作单元”,由 线程池(ThreadPool) 管理,所以实质上也是线程处理,与主线程不冲突。因为不用手动创建 / 销毁线程,更轻量、更高效、更易管理。
  • 任务的两种核心用途,即:1. 后台执行任务(多线程)不阻塞主线程,让代码在后台运行;2. 异步操作(I/O 操作)文件读写、网络请求、数据库查询等,不占用 CPU主线程。

二、Task 的基础实现

1、最简单基础的表达

  • Task.Run(() =>{…});是最Task任务最精简表达,参看下面的代码实例。
  • 注意System.Threading.Task记得加上,否则每次在调用时代码时写全。
  • => 后的大括号内可以直接执行任务也可以封装函数来运行。
using System;
using System.Threading.Tasks;
void MyTask()
{ 
       Console.WriteLine("任务开始");
       // 启动一个后台任务
       Task.Run(() =>
       {
           Console.WriteLine("任务开始执行");
           // 模拟耗时操作
           Task.Delay(2000).Wait(); 
           Console.WriteLine("任务执行完成");
       });

       Console.WriteLine("主线程继续执行"); 
}

2、带返回参数的表达

  • 可以看到下面代码中,定义了 Task 的类型为整形int作为返回值,因为我们调用的函数Sum()返回值就是int。
  • 这里用了比较标准的写法,也就是Task.Run + async/await的写法,async和await必须是同时出现的。
  • 等待任务完成并获取结果的"int result = task.Result;"这句话非常关键,没有这句话就不会等待2秒后的任务完成获取返回值,程序则会继续运行,result将得不到计算结果,result将输出结果为0。
//调用
private void MyTask()
{ 
   	Console.WriteLine("任务开始");
   	int a=1;int b=2;int result =0;
   	// 任务返回 int 类型结果
   	Task<int> task = Task.Run(() =>  Sum(a,b)); 
   	// 等待任务完成并获取结果//关键
   	result = task.Result; 
   	Console.WriteLine("结果:" + result); // 输出 3
}

//任务
private async int Sum(int A,int B)
{
   //模拟任务时间2秒
   await Task.Delay(2000);
   //返回值
   return (A+B); 
}

10:33:54:608 任务开始
10:33:56:613 任务内结果:3
10:33:56:613 结果:3

3、无返回值等待任务结束

  • 如果没返回值,一般情况是继续往下执行,以达到多线程异步处理,也就是不等待任务完成父线程继续往下执行,如果我们一样是需要等待呢?那,那,那也是可以实现的( ̄▽ ̄) ,运行任务后没有返回值等待,那我们就等待整个任务,await task等待任务运行结束。需要注意的是必须用async修饰定义该函数。
  • 可以看到到输出运行结果同前面的输出结果是一样的,我们换成了全局变量(R)来接收结果。
//全局变量 
int R = 0;
//调用
private async void MyTask()
{ 
    Console.WriteLine("任务开始");
    int a = 1; int b = 2; 
    // 任务返回 int 类型结果,不获取返回值
    Task task = Task.Run(() => Sum(a, b));
    await task;//等待运行结束 
   //等待任务完成后打印
   Console.WriteLine("结果:" + R); // 输出 3
}

//任务
private async Task<int> Sum(int A, int B)
{          
    //模拟任务时间2秒
    await Task.Delay(2000);  
     R = A+B;
      // 输出结果
    Console.WriteLine("任务内结果:" + R);
    return R;
}

输出
10:31:31:103 任务开始
10:31:33:092 任务内结果:3
10:31:33:092 结果:3

4、停止业务

  • 需要控制任务的停止,可以新建一个取消令牌源,可以叫它取消开关或标志。
  • 在调用任务前我可以定义一个毫秒时间来控制器停止,实例中给了1秒(1000),通过输出内容我们可以看到其停止的效果,和线程中停止线程循环的效果基本一样。
//新建取消令牌源
CancellationTokenSource cts = new CancellationTokenSource();
//调用
private async void MyTask()
{ 
	int a = 1; int b = 2;
	// 1秒后取消任务
	cts.CancelAfter(1000); 
	Console.WriteLine("任务开始");
	// 任务返回 int 类型结果
	Task<int> task = Task.Run(() => Sum(a, b),cts.Token);
	//等待并读取结果
	int result = task.Result;
	//输出打印任务结果
	Console.WriteLine("结果:" + result); 
}

//任务
private async Task<int> Sum(int A, int B)
{
    int R = 0;
    //查看标志循环
    while (!cts.Token.IsCancellationRequested)
    {
        //模拟循环
        await Task.Delay(100);
        R += A + B;
        Console.WriteLine("任务内结果:" + R);
    }               
     
    return R;
}

输出
10:20:52:514 任务开始
10:20:52:777 任务内结果:3
10:20:52:777 任务内结果:6
10:20:53:025 任务内结果:9
10:20:53:025 任务内结果:12
10:20:53:265 任务内结果:15
10:20:53:265 任务内结果:18
10:20:53:265 任务内结果:21
10:20:53:522 任务内结果:24
10:20:53:522 任务内结果:27
10:20:53:522 结果:27

4、其他功能和状态

  • 其它功能和状态获取简单介绍一下。

(1) 等待任务

task.Wait();          // 等待单个任务
Task.WaitAll(t1, t2); // 等待所有任务完成
Task.WaitAny(t1, t2); // 等待任意一个完成

(2)获取状态

task.IsCompleted      // 是否完成
task.IsFaulted        // 是否出错
task.IsCanceled       // 是否取消
task.Status           // 详细状态

三、总结

  • C#中的Task任务是个高级的多线程异步操作方案,优先用 Task.Run + async/await的格式来描述;
  • 注意await task和task.Wait()是有区别的,await task不阻塞线程,异步等待,线程可以去干别的,而task.Wait()阻塞线程,死等,线程卡死在这里不动。
  • 任务对比Thread线程 更轻量、高效、易维护,还支持返回值、等待、取消等等操作,这么高级的任务功能,大家可以用起来哟!
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐