c# Task 之任务取消

CLR via 一书中这样写到
创建一个 Task 时,可以将一个 CancellationToken 传递给Task的构造器,从而将这个Cancellation 和该 Task 关联起来。如果 CacellationToken 在 Task 调度前取消, Task 会被取消,永远都不会执行。但是,如果Task 已调度,那么Task 为了允许它的操作在执行期间取消,Task 的代码就必须显示支持取消。
来个试验:

	static void Main(string[] args) {
            CancellationTokenSource tokenSource = new CancellationTokenSource();
            Task task1 = Task.Factory.StartNew(
                () => taskRun("1"), tokenSource.Token
                );
            Thread.Sleep(100);
            tokenSource.Cancel();
			Thread.Sleep(2000);
            Console.WriteLine("task1是否取消:" + task1.IsCanceled);//task1.IsCanceled;
                                                               //Console.WriteLine("task2是否取消:" + task2.IsCanceled);
            Console.ReadKey();

        }

        static void taskRun(object obj) {
            string id = obj as string;

            for (int i = 0; i < 1000; i++) {
                Console.WriteLine("运行开始:" + i);
                Thread.Sleep(1);
            }
        }

开启一个任务,并在任务执行100ms 后调用CancellationTokenSource 的Cancel 函数。并在Cancel 执行后 输出 task1 是否取消
这个任务从0打印到999。每次打印后线程睡眠1ms。
c# Task 之任务取消_第1张图片
结果为任务未被取消!

我把 Sleep(100) 删除后, 也就是开启任务后,马上调用Cancel 函数
c# Task 之任务取消_第2张图片
从这里可以看出,在一个已经开始执行的任务时,单单使用Cancel 函数是不起作用的。任务根本不会被取消。

取消一个已经运行的任务
方法1:

		static void Main(string[] args) {
            CancellationTokenSource tokenSource = new CancellationTokenSource();
            var tk = tokenSource.Token;
            Task task1 = Task.Factory.StartNew(
                () => taskRun(tk), tk
                );
            //Thread.Sleep(100);
            tokenSource.Cancel();
            Thread.Sleep(2000);
            Console.WriteLine("完成!" + task1.IsFaulted + "," + task1.IsCompleted + "," + task1.IsCanceled);
            Console.ReadKey();

        }

        static void taskRun(object obj) {
            CancellationToken ct = (CancellationToken)obj;

            for (int i = 0; i < 1000; i++) {
                Console.WriteLine("运行开始:" + i);
                Thread.Sleep(1);

                ct.ThrowIfCancellationRequested();
            }
        }

注意:此代码需要在外部运行,不要使用编辑器调试。(编辑器调试的情况 ct.ThrowIfCancellationRequested 会引发异常,会弹窗中断无法得到正确的结果)。

运行结果
c# Task 之任务取消_第3张图片在任务中使用ct.ThrowIfCancellationRequested(); 如果任务在外部取消,将会抛出异常中断任务。在外部可以获得任务 已经完成 并且 已经取消的状态 这个要非常注意。
IsCompleted 属性可能不是 真的完成,还可能被取消了。要配合 IsCanceled 一起使用。
这里使用参数传递 CancellationToken,任务只支持一个参数,如果有多个参数要传递可以把参数包装成类,或者使用字典之类的容器来传递。

方法2:
自己定义取消标记,自己处理。

		//是否取消
        volatile static bool IsCancel = false;

        static void Main(string[] args) {
            Task task1 = Task.Factory.StartNew(
                ()=> taskRun()
                );
            Thread.Sleep(100);
            IsCancel = true;
            Thread.Sleep(2000);

            Console.WriteLine("完成!" + task1.IsFaulted + "," + task1.IsCompleted + "," + task1.IsCanceled);
            //Console.WriteLine("task1是否取消:" + task1.IsCanceled);//task1.IsCanceled;
                                                               //Console.WriteLine("task2是否取消:" + task2.IsCanceled);
            Console.ReadKey();

        }

        static void taskRun() {
            for (int i = 0; i < 1000; i++) {
                Console.WriteLine("运行开始:" + i);
                Thread.Sleep(1);

                if(IsCancel)
                    return;
            }
        }

运行结果
c# Task 之任务取消_第4张图片
这里使用一个全局字段来控制任务是否取消。
这里要注意的是用来表示任务取消标记的字段需使用volatile 关键字来标记字段为易变类型,否则可能无法正常取消任务!

每次从属性CancellationTokenSource.Token获得的CancellationToken(值类型)是不是同一个值?

CancellationTokenSource cts = new CancellationTokenSource();
            var tk1 = cts.Token;
            var tk2 = cts.Token;

            if(tk1 == tk2)
                Console.WriteLine("同一个Token!");
            else
                Console.WriteLine("不是同一个Token!");

运行后得到 “同一个Token!”

两个任务使用同一个CancellationTokenSource 取消任务会是怎么的结果?

  1. 如果在两个都还未运行的情况下,调用cancel() 函数,两个任务都会被取消,不会被运行。
  2. 先创建一个任务 然后调用cancel()函数,再创建一个任务。(未显示支持取消)。这时候不管第一个任务执行没执行。第二个任务肯定是不会执行。这里说明 cancel() 函数会把cancel() 调用之后创建的任务都取消掉(!
  3. 使用显示支持取消
        static void Main(string[] args) {
            CancellationTokenSource cts = new CancellationTokenSource();
            var tk1 = cts.Token;
            var tk2 = cts.Token;
            
            Task task1 = Task.Factory.StartNew(
                ()=> taskRun(new List(){ "1", tk1 }),tk1
                );
            Task task2 = Task.Factory.StartNew(
                () => taskRun(new List() { "2" }), tk1
                );

            Thread.Sleep(100);
            cts.Cancel();

            Thread.Sleep(2000);

            Console.WriteLine("完成1!" + task1.IsFaulted + "," + task1.IsCompleted + "," + task1.IsCanceled);
            Console.WriteLine("完成2!" + task2.IsFaulted + "," + task2.IsCompleted + "," + task2.IsCanceled);
            //Console.WriteLine("task1是否取消:" + task1.IsCanceled);//task1.IsCanceled;
            //Console.WriteLine("task2是否取消:" + task2.IsCanceled);
            Console.ReadKey();

        }

        static void taskRun(object obj) {
            var tp = obj as List;

            CancellationToken tk;
            string id = tp[0] as string;
            if(tp.Count > 1)
                tk = (CancellationToken)tp[1];

            for (int i = 0; i < 1000; i++) {
                Console.WriteLine("运行开始:" + i + "," + id);
                Thread.Sleep(1);

                if (tp.Count > 1)
                    tk.ThrowIfCancellationRequested();
            }
        }
 
  

运行结果
c# Task 之任务取消_第5张图片

我开启了两个任务,两个任务使用同一个CancelltionTokenSource以及同一个CancelltionToken,在第一个任务中使用 tk.ThrowIfCancellationRequested(); 来显示取消任务。当两个任务在运行中,调用 cancel() 后,第一个任务被成功取消,第二个任务未被取消。这个结果非常有趣,两个任务使用的是同一个Token, 却只取消调用ThrowIfCancellationRequested() 函数的任务! (我猜测是ThrowIfCancellationRequested函数会返回当前运行的线程,然后把区分任务,把当前任务停止!)

任务被取消,我读取任务的返回值会发生什么?
如果任务已被取消,使用返回值会抛出 System.AggregateException 异常。

任务取消是异步行为,使用Wait等待任务完成或取消会发生什么?
如果任务已被取消,使用Wait同样抛出 System.AggregateException 异常

你可能感兴趣的:(c#)