Vin*_*cio 55 c# multithreading c#-4.0
从我读到的有关任务的内容来看,以下代码应该取消当前正在执行的任务而不会抛出异常.我的印象是,任务取消的重点是礼貌地"要求"任务停止而不中止线程.
以下程序的输出是:
倾倒异常
[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;
}
Run Code Online (Sandbox Code Playgroud)
Jon*_*eet 89
我试图在取消时避免任何例外.
你不应该这样做.
投掷OperationCanceledException是在TPL中表达"你所取消的方法被取消"的惯用方式.不要反对 - 只是期待它.
这是一个很好的事情,因为这意味着当你有使用相同的取消标记多个操作,你不需要辣椒代码在与检查每一个层面看你刚刚调用的方法是否具有实际正常完成或是否因取消而退回.你可以CancellationToken.IsCancellationRequested随处使用,但从长远来看,它会让你的代码变得不那么优雅.
请注意,您的示例中有两段代码抛出异常 - 一个在任务本身内:
cancelToken.ThrowIfCancellationRequested()
Run Code Online (Sandbox Code Playgroud)
还有一个等待任务完成的地方:
task.Wait(cancellationToken.Token);
Run Code Online (Sandbox Code Playgroud)
我不认为你真的想要将取消令牌传递给task.Wait电话,说实话......这允许其他代码取消你的等待.鉴于你知道你刚刚取消了这个令牌,这是毫无意义的 - 它必然会抛出异常,无论该任务是否实际上已经注意到取消.选项:
Jos*_*osh 69
您在此行明确抛出异常:
cancelToken.ThrowIfCancellationRequested();
Run Code Online (Sandbox Code Playgroud)
如果你想优雅地退出任务,那么你只需要摆脱那条线.
通常人们使用它作为控制机制来确保当前处理被中止而不会运行任何额外的代码.此外,在呼叫时无需检查取消,ThrowIfCancellationRequested()因为它在功能上等同于:
if (token.IsCancellationRequested)
throw new OperationCanceledException(token);
Run Code Online (Sandbox Code Playgroud)
使用ThrowIfCancellationRequested()您的任务可能看起来更像这样:
int CalculatePrime(CancellationToken cancelToken, object digits) {
try{
while(true){
cancelToken.ThrowIfCancellationRequested();
//Long operation here...
}
}
finally{
//Do some cleanup
}
}
Run Code Online (Sandbox Code Playgroud)
此外,Task.Wait(CancellationToken)如果令牌被取消,将抛出异常.要使用此方法,您需要将Wait调用包装在一个Try...Catch块中.
上面的一些答案看起来好像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;
}
}
Run Code Online (Sandbox Code Playgroud)
您可以
这可以解决内循环中保证最小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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
有更快的算法(我将不讨论,因为我是远远不够题外话),但这优化是很容易的,还是证明我的观点:不要担心微观优化的运行时,当你的算法是这样远最佳.
小智 8
关于使用ThrowIfCancellationRequested而不是的好处的另一个注意事项IsCancellationRequested:我发现当需要使用ContinueWith延续选项时TaskContinuationOptions.OnlyOnCanceled,IsCancellationRequested不会导致条件ContinueWith反射.ThrowIfCancellationRequested但是,将设置任务的已取消条件,从而导致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();
Run Code Online (Sandbox Code Playgroud)