c# 高级编程 21章470页 【任务和并行编程】【取消】

504 阅读2分钟

取消

目前,以下技术支持以标准方式取消长时间运行的操作:任务、并发集合类、并行LINQ、其他几种同步机制。

取消,是协作的,它不是强迫的

长时间运行的任务会检查它是否被取消,并相应地返回控制权

支持取消的方法,接收一个CancellationToken参数。

  • 长时间运行的操作,检查取消的方式:
    1. IsCancellationRequested属性
    2. 取消时,使用CancellationTokenWaitHandle属性
    3. 使用CancellationTokenRegister方法。Register()接收以下参数:
      • Action:在取消时调用
      • ICancelableOperation,实现这个接口的对象的Cancel()方法在执行取消操作时调用

Parallel.For() 的取消

ParallelOptionsCancellationToken

  • CancellationToken
    • 通过CancellationTokenSource来生成
    • 可以调用Register()方法来注册取消操作时要做的事情
  • CancellationTokenSource
    • 实现了ICancelableOperation接口
    • 可以调用Cancel()方法来取消操作
  • Parallel
    • 验证CancellationToken的结果,并取消操作
    • 一旦取消操作,For()就抛出一个OperationCanceledException
        public static void CancelParallelFor()
        {
            Console.WriteLine(nameof(CancelParallelFor));
            var cts = new CancellationTokenSource();
            cts.Token.Register(() => Console.WriteLine("*** token cancelled"));

            // send a cancel after 500 ms
            cts.CancelAfter(500);

            try
            {
                ParallelLoopResult result =
                  Parallel.For(0, 100, new ParallelOptions
                  {
                      CancellationToken = cts.Token,
                  },
                  x =>
                  {
                      Console.WriteLine($"loop {x} started");
                      int sum = 0;
                      for (int i = 0; i < 100; i++)
                      {
                          Task.Delay(2).Wait();
                          sum += i;
                      }
                      Console.WriteLine($"loop {x} finished");
                  });
            }
            catch (OperationCanceledException ex)
            {
                Console.WriteLine(ex.Message);
            }
            Console.WriteLine();
        }

输出:

CancelParallelFor
loop 0 started
loop 24 started
loop 12 started
loop 36 started
loop 48 started
loop 60 started
loop 72 started
loop 84 started
loop 96 started
*** token cancelled
loop 24 finished
loop 84 finished
loop 0 finished
loop 36 finished
loop 96 finished
loop 12 finished
loop 48 finished
loop 72 finished
loop 60 finished
The operation was canceled.

Task 的取消

  1. 先新建一个CancellationTokenSource
    • 如果只需要一个CancellationToken的话,可以使用默认的:Task.Factory.CancellationToken
  2. 调用CancellationTokenSourceCancel()CancelAfter()
  3. CancellationToken作为第二个参数,传递给Task.Run()方法
  4. 检查CancellationTokenIsCancellationRequested
  5. 调用CancellationTokenThrowIfCancellationRequested
        public static void CancelTask()
        {
            Console.WriteLine(nameof(CancelTask));
            var cts = new CancellationTokenSource();
            cts.Token.Register(() => Console.WriteLine("*** task cancelled"));
            // send a cancel after 500 ms
            cts.CancelAfter(500);
            Task t1 = Task.Run(() =>
            {
                Console.WriteLine("in task");
                for (int i = 0; i < 20; i++)
                {
                    Task.Delay(100).Wait();
                    CancellationToken token = cts.Token;
                    if (token.IsCancellationRequested)
                    {
                        Console.WriteLine("cancelling was requested, " +
                          "cancelling from within the task");
                        token.ThrowIfCancellationRequested();
                        break;
                    }
                    Console.WriteLine("in loop");
                }
                Console.WriteLine("task finished without cancellation");
            }, cts.Token);
            try
            {
                t1.Wait();
            }
            catch (AggregateException ex)
            {
                Console.WriteLine($"exception: {ex.GetType().Name}, {ex.Message}");
                foreach (var innerException in ex.InnerExceptions)
                {
                    Console.WriteLine($"inner exception: {innerException.GetType()}," +
                      $"{innerException.Message}");
                }
            }
            Console.WriteLine();
        }

image.png

输出:

CancelTask
in task
in loop
in loop
in loop
in loop
*** task cancelled
cancelling was requested, cancelling from within the task
exception: AggregateException, One or more errors occurred. (A task was canceled.)
inner exception: System.Threading.Tasks.TaskCanceledException,A task was canceled.