C# task 取消

时间:2025-03-26 07:46:21

1、需求 
 我们知道task是并行计算的,比如说主线程在某个时刻由于某种原因要取消某个task的执行,我们能做到吗? 当然我们可以做到。 
在4.0中给我们提供一个“取消标记”叫做,在创建task的时候传入此参数,就可以将主线程和任务相关联,然后在任务中设置“取消信号“叫做ThrowIfCancellationRequested来等待主线程使用Cancel来通知,一旦cancel被调用。task将会抛出OperationCanceledException来中断此任务的执行,最后将当前task的Status的IsCanceled属性设为true。 
注意:一定要处理这个异常,可以通过调用成员来获取这个异常。如果一直不查询Task的Exception属性。你的代码就永远注意不到这个异常的发生,如果不能捕捉到这个异常,垃圾回收时,抛出AggregateException,进程就会立即终止,这就是“牵一发动全身”,莫名其妙程序就自己关掉了,谁也不知道这是什么情况。所以,必须调用前面提到的某个成员,确保代码注意到异常,并从异常中恢复。因此可以将条用Task的某个成员来检查Task是否跑出了异常,通常调用Task的Result。下面看代码:

        static void Main(string[] args)
        {
            var cts = new CancellationTokenSource();
            var ct = ;

            Task task1 = new Task(() => { Run1(ct); }, ct);

            Task task2 = new Task(Run2);

            try
            {
                ();
                ();
//在这段时间内,();是不会被触发的
                (1000);
                ();
                //这时候会触发();

                (task1, task2);
            }
            catch (AggregateException ex)
            {
                foreach (var e in )
                {
                    ("\nhi,我是OperationCanceledException:{0}\n", );
                }

                //task1是否取消
                //("task1是不是被取消了? {0}", );
                //("task2是不是被取消了? {0}", );
            }
            ("task1是不是被取消了? {0}", );
            ("task2是不是被取消了? {0}", );
            ();
        }

        static void Run1(CancellationToken ct)
        {
        //下面这句话是没有用的,因为这个时候没有收到Cancel的消息
            ();

            ("我是任务1");

            (2000);//是为了在Cancel的时候Run1没有执行完
//当上面等到1000ms多的时候实际上已经接收到Cancel的消息了,但是这个时候不会取消task的,只有调用下面这句话的时候才会取消task
            ();

            ("我是任务1的第二部分信息");
        }

        static void Run2()
        {
            ("我是任务2");
        }123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354

注意: 
1、只有();才能真正的取消Task,此外,();的调用必须是接收到Cancel命令过来才会触发的。 
2、个人认为:这样子的目的是为了接收到取消的时候,不会立马停止Task,而是只有在合适的时间停止。 
2、Task的写法:

1./yunfeifei/p/
2./zh-cn/library/?cs-save-lang=1&cs-lang=csharp#code-snippet-1

3./yunfeifei/p/

4./x-xk/tag/C%23线程/

var cts = new CancellationTokenSource();
var ct = ;
Task task1 = new Task(() => { Run1(ct); }, ct);
 

///

类介绍:

Task 类的表示单个操作不返回一个值,通常以异步方式执行。 Task 对象是一个的中心思想 基于任务的异步模式 首次引入.NET Framework 4 中。 因为由执行工作 Task 对象通常以异步方式执行在线程池线程上而不是以同步方式在主应用程序线程,您可以使用 Status 属性,以及 IsCanceled, ,IsCompleted, ,和 IsFaulted 属性,以确定任务的状态。 大多数情况下,lambda 表达式用于指定的任务是执行的工作。

对于返回值的操作,您使用 Task 类。

任务Task和线程Thread的区别:

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

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

Task和Thread一样,位于命名空间下!

一、创建Task

Task 类还提供了构造函数对任务进行初始化,但的未计划的执行。 出于性能原因, 或 (工厂创建) 方法是用于创建和计划计算的任务的首选的机制,但对于创建和计划必须分开的方案,您可以使用的构造函数(new一个出来),然后调用 方法来计划任务,以在稍后某个时间执行。

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

//第二种创建方式,工厂创建,直接执行
   var task2 = (() =>
    {
     //TODO you code
    });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

二、Task的简略生命周期:

方法名 说明
Created 表示默认初始化任务,但是“工厂创建的”实例直接跳过。
WaitingToRun 这种状态表示等待任务调度器分配线程给任务执行。
RanToCompletion 任务执行完毕。
//查看Task中的状态
   var task1 = new Task(() =>
         {
            ("Begin");
            (2000);
            ("Finish");
         });
         ("Before start:" + );
         ();
         ("After start:" + );
         ();
         ("After Finish:" + );

         ();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

三、Task的任务控制:Task最吸引人的地方就是他的任务控制了,你可以很好的控制task的执行顺序,让多个task有序的工作

方法名 说明
();就是等待任务执行(task1)完成,task1的状态变为Completed。
待所有的任务都执行完成:
发同,就是等待任何一个任务完成就继续向下执行
第一个Task完成后自动启动下一个Task,实现Task的延续
CancellationTokenSource 通过cancellation的tokens来取消一个Task。

下面详细介绍一下上面的几个方法:

1、

();就是等待任务执行(task1)完成,task1的状态变为Completed。

2、

看字面意思就知道,就是等待所有的任务都执行完成:

 {
(task,task2,task3...N)
("All task finished!");
}
  • 1
  • 2
  • 3
  • 4

即当task,task2,task3…N全部任务都执行完成之后才会往下执行代码(打印出:“All task finished!”)

3、

这个用发同,就是等待任何一个任务完成就继续向下执行,将上面的代码WaitAll替换为WaitAny

 {
(task,task2,task3...N)
("Any task finished!");
}
  • 1
  • 2
  • 3
  • 4

即当task,task2,task3…N任意一个任务都执行完成之后就会往下执行代码(打印出:” Any task finished!”)

4、

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

static void Main(string[] args)
        {
            var task1 = new Task(() =>
            {
                ("Task 1 Begin");
                (2000);
                ("Task 1 Finish");
            });
            var task2 = new Task(() =>
            {
                ("Task 2 Begin");
                (3000);
                ("Task 2 Finish");
            });
            ();
            ();
            var result = <string>(task =>
            {
                ("task1 finished!");
                return "This is task result!";
            });
            (());
            ();
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

可以看到,task1完成之后,开始执行后面的内容,并且这里我们取得task的返回值。

5、Task的取消

前面说了那么多Task的用法,下面来说下Task的取消,比如我们启动了一个task,出现异常或者用户点击取消等等,我们可以取消这个任务。如何取消一个Task呢,我们通过cancellation的tokens来取消一个Task。在很多Task的Body里面包含循环,我们可以在轮询的时候判断IsCancellationRequested属性是否为True,如果是True的话就return或者抛出异常,抛出异常后面再说,因为还没有说异常处理的东西。

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

var tokenSource = new CancellationTokenSource();
            var token = ;
            var task = (() =>
            {
                for (var i = 0; i < 1000; i++)
                {
                    (1000);
                    if ()
                    {
                        ("Abort mission success!");
                        return;
                    }
                }
            }, token);
            (() =>
            {
                ("Canceled");
            });
            ("Press enter to cancel task...");
            ();
            ();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

这里开启了一个Task,并给token注册了一个方法,输出一条信息,然后执行ReadKey开始等待用户输入,用户点击回车后,执行方法,取消任务。