四、取消架构

.NET包含一个取消架构,允许以标准方式取消长时间运行的任务。每个阻塞调用都应该支持这种机制。当然目前并不是所有阻塞调用都实现了这个新技术,但越来越多的阻塞调用都支持它。已经提供了这种机制的技术有任务、并发集合类、并行LINQ和几种同步机制。

取消架构基于协作行为,它不是强制的。长时间运行的任务会检查它是否被取消,并相应地返回控制权。

支持取消的方法接受一个CancellationToken参数。这个类定义了IsCancellationRequested属性,其中长时间运行的操作可以检查它是否应终止。长时间运行的操作检查取消的其他方式有:取消标记时,使用标记的WaitHandle属性,或者使用Register()方法。Register()方法接受Action和ICancelableOperation类型的参数。Action委托引用的方法在取消标记时调用。这类似于ICancelableOperation,其中实现这个接口的对象的Cancel()方法在执行取消操作时调用。

CancellationSamples的示例代码使用如下名称空间:

System

System.Threading

System.Threading.Tasks

1. Parallel.For()方法的取消

本节以一个使用Parallel.For()方法的简单例子开始。Parallel类提供了For()方法的重载版本,在重载版本中,可以传递ParallelOptions类型的参数。使用ParallelOptions类型,可以传递一个CancellationToken参数。CancellationToken参数通过创建CancellationTokenSource来生成。由于CancellationTokenSource实现了ICancelableOperation接口,因此可以用CancellationToken注册,并允许使用Cancel()方法取消操作。本例没有直接调用Cancel()方法,而是使用了方法CancelAfter(),在500毫秒后取消标记。

在For()循环的实现代码内部,Parallel类验证CancellationToken的结果,并取消操作。一旦取消操作,For()方法就抛出一个OperationCancelException类型的异常,这是本例捕获的异常。使用CancellationToken可以注册取消操作时的信息。为此,需要调用Register()方法,并传递一个在取消操作时调用的委托。

        static void 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);
            }
        }

运行应用程序,会得到类似如下的结果,第16、64、0、32、48、80和96次迭代都启动了。这在一个有6个内核CPU的系统上运行。通过取消操作,所有其他的迭代操作都在启动之前就取消了。启动的迭代操作允许完成,因为取消操作总是以协作方式运行,以避免在取消迭代操作的中间泄漏资源。

loop 16 started
loop 64 started
loop 0 started
loop 32 started
loop 48 started
loop 80 started
loop 96 started
*** token cancelled
loop 48 finished
loop 64 finished
loop 96 finished
loop 32 finished
loop 16 finished
loop 0 finished
loop 80 finished
The operation was canceled.

2. 任务的取消

同样的取消模式也可用于任务。首先,新建一个CancellationTokenSource。如果仅需要一个取消标记,就可以通过访问Task.Factory.CancellationToken以使用默认的取消标记。接着,与前面的代码类似,在500毫秒后取消任务。在循环中执行主要工作的任务通过TaskFactory对象接受取消标记。在构造函数中,把取消标记赋予TaskFactory。这个取消标记由任务用于检查CancellationToken的IsCancellationRequested属性,以确定是否请求了取消。(该段文字描述与下面的示例代码不符)

        static void 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(() =>
            {
                CancellationToken token = cts.Token;
                Console.WriteLine("in task");
                for (int i = 0; i < 20; i++)
                {
                    Task.Delay(100).Wait();                    
                    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: {ex.InnerExceptions.GetType().Name}, {ex.InnerException.Message}");
                }
            }
        }

运行应用程序,可以看到任务启动了,运行了几个循环,并获得了取消请求。之后取消任务,并抛出AggregateException异常,它是从方法调用ThrowCancellationRequester()中启动的。调用者等待任务时,会捕获AggregateException异常,它包含内部异常。例如,如果在一个也被取消的任务中运行Parallel.For()方法,这就可以用于取消的层次结构。任务的最终状态是Canceled。

运行结果:

in task
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: ReadOnlyCollection`1, A task was canceled.

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值