从我读到的有关任务的内容中,以下代码应该取消当前正在执行的任务而不会抛出异常。我的印象是,取消任务的重点是礼貌地“要求”任务停止而不中止线程。
以下程序的输出是:
转储例外
[OperationCanceledException]
取消并返回最后计算的素数。
我试图在取消时避免任何例外。我怎么能做到这一点?
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;
}
答案 0 :(得分:84)
我试图在取消时避免任何例外。
你不应该这样做。
投掷OperationCanceledException
是在TPL中表达“您调用的方法被取消”的惯用方式。不要反对 - 只是期待它。
这是好的事情,因为这意味着当您使用相同的取消令牌进行多项操作时,您无需在每个级别对您的代码进行检查以查看是否或不是你刚刚调用的方法实际上已经正常完成,或者是否由于取消而返回。你可以在任何地方使用CancellationToken.IsCancellationRequested
,但从长远来看,它会使你的代码不那么优雅。
请注意,您的示例中有两个代码,它们会抛出异常 - 一个在任务本身内:
cancelToken.ThrowIfCancellationRequested()
以及等待任务完成的地方:
task.Wait(cancellationToken.Token);
我认为你真的不想将取消令牌传递给task.Wait
电话,说实话......允许其他代码取消你的等待。鉴于你知道你刚刚取消了这个令牌,这是毫无意义的 - 它是绑定来抛出异常,无论该任务是否实际上已经注意到取消。选项:
答案 1 :(得分:65)
你明确地在这一行上抛出一个异常:
cancelToken.ThrowIfCancellationRequested();
如果你想优雅地退出任务,那么你只需要摆脱那条线。
通常人们使用它作为控制机制来确保当前处理被中止而不会运行任何额外的代码。此外,在调用ThrowIfCancellationRequested()
时无需检查取消,因为它在功能上等同于:
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)
将抛出异常。要使用此方法,您需要将Wait调用包装在Try...Catch
块中。
答案 2 :(得分:8)
上面的一些答案读起来好像ThrowIfCancellationRequested()
是一个选项。在这种情况下不,因为你不会得到你最后的素数。 idiomatic way that "the method you called was cancelled"
是针对取消意味着丢弃任何(中间)结果的情况定义的。如果您的取消定义是“停止计算并返回最后的中间结果”,那么您就已经离开了。
讨论特别是在运行时方面的好处也很容易让人误解: 实现的算法在运行时很糟糕。即使是高度优化的取消也不会有任何好处。
最简单的优化是展开此循环并跳过一些不必要的循环:
for(i=2; i <= num/2; i++) {
if((num % i) == 0) {
// num is evenly divisible -- not prime
isprime = false;
factor = i;
}
}
你可以
这意味着在内循环中保存最小75%(粗略估计:90%)的循环,只需将其替换为:
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;
}
}
}
有更快的算法(我不会讨论,因为我离主题很远),但这种优化非常简单,仍然证明了我的观点: 当你的算法这个远非最优时,不要担心微优化运行时。
答案 3 :(得分:7)
关于使用ThrowIfCancellationRequested
而不是IsCancellationRequested
的好处的另一个注意事项:我发现当需要使用ContinueWith
的{{1}}的延续选项TaskContinuationOptions.OnlyOnCanceled
时,{{ 1}}不会导致条件IsCancellationRequested
触发。 ContinueWith
但是,将设置任务的已取消条件,导致ThrowIfCancellationRequested
触发。
注意:仅当任务已在运行时才会出现,而在任务启动时则不然。这就是我在开始和取消之间添加ContinueWith
的原因。
Thread.Sleep()
答案 4 :(得分:0)
您有两件事在监听令牌,compute prime方法和名为task的Task实例。 calculate prime方法应正常返回,但任务仍在运行时被取消,因此引发。当您构造任务时,不要费心给它令牌。