取消任务是抛出一个exception
从我读过的任务中,下面的代码应该取消当前执行的任务而不会抛出exception。 我的印象是,取消任务的全部任务就是礼貌地“要求”任务停止而不中止线程。
以下程序的输出是:
倾销例外
[OperationCanceledException]
取消并返回上次计算的素数。
我试图在取消时避免任何exception。 我怎样才能做到这一点?
void Main() { var cancellationToken = new CancellationTokenSource(); var task = new Task<int>(() => { return CalculatePrime(cancellationToken.Token, 10000); }, cancellationToken.Token); try { task.Start(); Thread.Sleep(100); cancellationToken.Cancel(); task.Wait(cancellationToken.Token); } catch (Exception e) { Console.WriteLine("Dumping exception"); e.Dump(); } } int CalculatePrime(CancellationToken cancelToken, object digits) { int factor; int lastPrime = 0; int c = (int)digits; for (int num = 2; num < c; num++) { bool isprime = true; factor = 0; if (cancelToken.IsCancellationRequested) { Console.WriteLine ("Cancelling and returning last calculated prime."); //cancelToken.ThrowIfCancellationRequested(); return lastPrime; } // see if num is evenly divisible for (int i = 2; i <= num/2; i++) { if ((num % i) == 0) { // num is evenly divisible -- not prime isprime = false; factor = i; } } if (isprime) { lastPrime = num; } } return lastPrime; }
你明确地在这一行上抛出一个Exception:
cancelToken.ThrowIfCancellationRequested();
如果你想优雅地退出任务,那么你只需要摆脱那条线。
通常人们使用这个作为控制机制来确保当前处理被中止,而不会潜在地运行任何额外的代码。 此外,调用ThrowIfCancellationRequested()
时不需要检查取消,因为它在function上等同于:
if (token.IsCancellationRequested) throw new OperationCanceledException(token);
当使用ThrowIfCancellationRequested()
你的任务可能看起来更像这样:
int CalculatePrime(CancellationToken cancelToken, object digits) { try{ while(true){ cancelToken.ThrowIfCancellationRequested(); //Long operation here... } } finally{ //Do some cleanup } }
此外,如果令牌被取消, Task.Wait(CancellationToken)
将引发exception。 要使用此方法,您需要将您的等待呼叫包裹在Try...Catch
块中。
MSDN:如何取消任务
我试图在取消时避免任何exception。
你不应该这样做。
抛出OperationCanceledException
是TPL中expression“你调用的方法被取消”的惯用方式。 不要反对 – 只是期待它。
这是一件好事,因为这意味着当您使用相同的取消标记进行多个操作时,不需要在每个级别上都用检查来查看您的代码,以查看刚才调用的方法是否实际上正常完成或是否因取消而退回。 你可以在任何地方使用CancellationToken.IsCancellationRequested
,但是从长远来看,它会使你的代码不那么优雅。
请注意,在你的例子中有两个代码是抛出一个exception – 一个在任务本身:
cancelToken.ThrowIfCancellationRequested()
还有一个等待任务完成的地方:
task.Wait(cancellationToken.Token);
我不认为你真的想将取消令牌传递给task.Wait
电话,说实话…允许其他代码取消你的等待 。 鉴于你知道你刚刚取消了这个标记,这是毫无意义的 – 它肯定会抛出一个exception,无论这个任务是否真的注意到了这个取消。 选项:
- 使用不同的取消标记(以便其他代码可以独立取消您的等待)
- 使用超时
- 只要等待就可以了
一些上述的答案阅读,如果ThrowIfCancellationRequested()
将是一个选项。 这不是在这种情况下,因为你不会得到你最后的素数。 idiomatic way that "the method you called was cancelled"
是取消手段(中间)结果的情况。 如果你的取消定义是“停止计算并返回最后的中间结果”,那么你已经离开了。
讨论特别是运行时的好处也是相当具有误导性的:实现的algorithm在运行时很糟糕。 即使是高度优化的取消也不会有任何好处。
最简单的优化是展开这个循环并跳过一些不必要的循环:
for(i=2; i <= num/2; i++) { if((num % i) == 0) { // num is evenly divisible -- not prime isprime = false; factor = i; } }
您可以
- 每个偶数保存(num / 2)-1个周期,总体上略小于50%(展开),
- 为每个素数保存(num / 2)-square_root_of(num)个循环(根据最小素数的mathselect边界),
- 至less为每一个非素数节省很多,期望更多的储蓄,例如num = 999结束1个周期而不是499(如果find答案,rest)和
- 保存另外50%的循环,这当然是25%(根据素数select步骤,展开处理特例2)。
这就解释了在内循环中保存75%(粗略估计:90%)循环的保证最小值,只需将其replace为:
if ((num % 2) == 0) { isprime = false; factor = 2; } else { for(i=3; i <= (int)Math.sqrt(num); i+=2) { if((num % i) == 0) { // num is evenly divisible -- not prime isprime = false; factor = i; break; } } }
有更快的algorithm(我不会讨论,因为我远离主题),但是这个优化是很容易的,仍然certificate我的观点:不要担心微algorithm运行时,当你的algorithm是这么远最佳。
关于使用ThrowIfCancellationRequested
而不是IsCancellationRequested
的好处的另一个注意事项:我发现当需要使用ContinueWith
和TaskContinuationOptions.OnlyOnCanceled
的继续选项时, IsCancellationRequested
不会导致条件化的ContinueWith
触发。 然而, ThrowIfCancellationRequested
将设置任务的Cancelled条件,导致ContinueWith
触发。
注意:只有在任务已经运行的时候,这个才是真的。 这就是为什么我在开始和取消之间添加了一个Thread.Sleep()
。
CancellationTokenSource cts = new CancellationTokenSource(); Task task1 = new Task(() => { while(true){ if(cts.Token.IsCancellationRequested) break; } }, cts.Token); task1.ContinueWith((ant) => { // Perform task1 post-cancellation logic. // This will NOT fire when calling cst.Cancel(). } Task task2 = new Task(() => { while(true){ cts.Token.ThrowIfCancellationRequested(); } }, cts.Token); task2.ContinueWith((ant) => { // Perform task2 post-cancellation logic. // This will fire when calling cst.Cancel(). } task1.Start(); task2.Start(); Thread.Sleep(3000); cts.Cancel();