详情

首页手游攻略 C# 并行和多线程编程——认识和使用Task实用指南

C# 并行和多线程编程——认识和使用Task实用指南

佚名 2026-10-27 08:40:01

平时做技术实践时,很多问题不是概念不会,而是细节没串起来。拿“C# 并行和多线程编程——认识和采用Task”来说,它看着像小点,放到项目里常会牵出环境、配置、兼容性和维护成本。下面按实际采用顺序,把思路、关键写法和容易踩坑的地方讲清楚,便于大家直接对照操作。

落到代码里,对于多线程,我们经常采用的是Thread。在我们了解Task之前,如果我们要采用多核的功能可能就会自己来开线程,然而这种线程模型在.net 4.0之后被一种称为基于“任务的编程模型”所冲击,因为task会比thread具有更小的性能开销,不过大家肯定会有疑惑,任务和线程到底有什么区别呢?

任务和线程的区别:

1、任务是架构在线程之上的,也就是说任务最后还是要抛给线程去执行。

2、任务跟线程不是一对一的关系,比如开10个任务同时不是说会开10个线程,这一点任务有点类似线程池,但是任务相比线程池有很小的开销和精确的控制。

一、认识Task和Task的基本采用

1、认识Task

首先来看一下Task的继承结构。Task标识一个异步操作。

落到代码里,能够看到Task和Thread一样,位于System.Threading命名空间下,这也就是说他们直接有密不可分的联系。下面我们来仔细看一下吧!

2、新建Task

理解这一步时,新建Task的方法有两种,一种是直接新建——new一个出来,一种是借助工厂新建。下面来看一下这两种新建方法:


    //第一种创建方式,直接实例化
     var task1 = new Task(() =>
     {
      //TODO you code
     });

理解这一步时,这是最轻松的新建方法,能够看到其构造函数是一个Action,其构造函数有如下所示几种,比较常用的是前两种。


    //第二种创建方式,工厂创建
     var task2 = Task.Factory.StartNew(() =>
     {
      //TODO you code
     });

理解这一步时,这种方式借助静态工厂,新建以个Task并运行。下面我们来建一个控制台项目,演示一下,代码如下所示:

要添加System.Threading.Tasks命名控件引用。


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TaskDemo
{
  class Program
  {
   static void Main(string[] args)
   {
     var task1 = new Task(() =>
     {
      Console.WriteLine("Hello,task");
     });
     task1.Start();

     var task2 = Task.Factory.StartNew(() =>
     {
      Console.WriteLine("Hello,task started by task factory");
     });

     Console.Read();
   }
  }
}

结合项目来看,这里我分别用两种方式新建两个task,并让他们运行。能够看到借助构造函数新建的task,必须手动Start,而借助工厂新建的Task直接就启动了。

下面我们来看一下Task的声明周期,编写如下所示代码:


var task1 = new Task(() =>
     {
      Console.WriteLine("Begin");
      System.Threading.Thread.Sleep(2000);
      Console.WriteLine("Finish");
     });
     Console.WriteLine("Before start:" + task1.Status);
     task1.Start();
     Console.WriteLine("After start:" + task1.Status);
     task1.Wait();
     Console.WriteLine("After Finish:" + task1.Status);

     Console.Read();

task1.Status就是输出task的当前状态,其输出结果如下所示:

实际处理时,能够看到调用Start前的状态是Created,随后等待分配线程去执行,到最后执行完成。

从我们能够得出Task的简略生命周期:

Created:表示默认初始化任务,但是“工厂新建的”实例直接跳过。

WaitingToRun: 这种状态表示等待任务调度器分配线程给任务执行。

RanToCompletion:任务执行完毕。

二、Task的任务控制

结合项目来看,Task最吸引人的地方就是他的任务控制了,你能够很好的控制task的执行顺序,让多个task有序的工作。下面来详细说一下:

1、Task.Wait

结合项目来看,在上个例子里,我们已经采用过了,task1.Wait();就是等待任务执行完成,我们能够看到最后task1的状态变为Completed。

2、Task.WaitAll

实际处理时,看字面意思就知道,就是等待所有的任务都执行完成,下面我们来写一段代码演示一下:


static void Main(string[] args)
   {
     var task1 = new Task(() =>
     {
      Console.WriteLine("Task 1 Begin");
      System.Threading.Thread.Sleep(2000);
      Console.WriteLine("Task 1 Finish");
     });
     var task2 = new Task(() =>
     {
      Console.WriteLine("Task 2 Begin");
      System.Threading.Thread.Sleep(3000);
      Console.WriteLine("Task 2 Finish");
     });
     
     task1.Start();
     task2.Start();
     Task.WaitAll(task1, task2);
     Console.WriteLine("All task finished!");

     Console.Read();
   }

其输出结果如下所示:

落到代码里,能够看到,任务一和任务二都完成以后,才输出All task finished!

3、Task.WaitAny

理解这一步时,这个用发同Task.WaitAll,就是等待任何一个任务完成就继续向下执行,将上面的代码WaitAll替换为WaitAny,输出结果如下所示:

4、Task.ContinueWith

理解这一步时,就是在第一个Task完成后自动启动下一个Task,实现Task的延续,下面我们来看下他的用法,编写如下所示代码:


static void Main(string[] args)
   {
     var task1 = new Task(() =>
     {
      Console.WriteLine("Task 1 Begin");
      System.Threading.Thread.Sleep(2000);
      Console.WriteLine("Task 1 Finish");
     });
     var task2 = new Task(() =>
     {
      Console.WriteLine("Task 2 Begin");
      System.Threading.Thread.Sleep(3000);
      Console.WriteLine("Task 2 Finish");
     });

     
     task1.Start();
     task2.Start();
     var result = task1.ContinueWith<string>(task =>
     {
      Console.WriteLine("task1 finished!");
      return "This is task result!";
     });

     Console.WriteLine(result.Result.ToString());


     Console.Read();
   }

执行结果如下所示:

在这个场景下,能够看到,task1完成之后,开始执行后面的内容,同时且这里我们取得task的得到值。

从实现思路看,在每次调用ContinueWith方法时,每次会把上次Task的引用传入进来,以便检测上次Task的状态,比如能够采用上次Task的Result属性来拿到得到值。我们还能够这么写:


var SendFeedBackTask = Task.Factory.StartNew(() => { Console.WriteLine("Get some Data!"); })
              .ContinueWith<bool>(s => { return true; })
              .ContinueWith<string>(r =>
               {
                 if (r.Result)
                 {
                  return "Finished";
                 }
                 else
                 {
                  return "Error";
                 }
               });
     Console.WriteLine(SendFeedBackTask.Result);

结合项目来看,首先输出Get some data,随后执行第二个获得得到值true,最后根据判断得到Finished或error。输出结果:

Get some Data!

Finished

其实上面的写法简化一下,能够这样写:


Task.Factory.StartNew<string>(() => {return "One";}).ContinueWith(ss => { Console.WriteLine(ss.Result);});

输出One,这个能够看明白了吧~

更多ContinueWith用法参见:(链接已移除)

5、Task的取消

在这个场景下,前面说了那么多Task的用法,下面来说下Task的取消,比如我们启动了一个task,出现异常或者用户点击取消等等,我们能够取消这个任务。

实际处理时,如何取消一个Task呢,我们借助cancellation的tokens来取消一个Task。在很多Task的Body里面包含循环,我们能够在轮询的时候判断IsCancellationRequested属性是否为True,如果是True的话就return或者抛出异常,抛出异常后面再说,因为还没有说异常处理的东西。

下面在代码中看下如何实现任务的取消,代码如下所示:


var tokenSource = new CancellationTokenSource();
     var token = tokenSource.Token;
     var task = Task.Factory.StartNew(() =>
     {
      for (var i = 0; i < 1000; i++)
      {
        System.Threading.Thread.Sleep(1000);
        if (token.IsCancellationRequested)
        {
         Console.WriteLine("Abort mission success!");
         return;
        }
      }
     }, token);
     token.Register(() =>
     {
      Console.WriteLine("Canceled");
     });
     Console.WriteLine("Press enter to cancel task...");
     Console.ReadKey();
     tokenSource.Cancel();
     Console.ReadKey();//这句忘了加,程序退出了,看不到“Abort mission success!“这个提示

实际处理时,这里开启了一个Task,同时给token注册了一个方法,输出一条信息,然后执行ReadKey开始等待用户输入,用户点击回车后,执行tokenSource.Cancel方法,取消任务。其输出结果如下所示:

从实现思路看,好了,今天先说道这里,明天继续讲task,下面该说说task的异常处理和其他的一些用法,如果喜欢能够关注我,一有更新会马上通知你。

作者:雲霏霏

博客地址:(链接已移除)

落到代码里,总的来说,c#适合结合实际项目边做边理解。先抓住核心思路,再逐步补上细节和边界处理,最后效果会更稳定,也更容易复用。

点击查看更多
推荐专题
热门阅读