好的,我的问题很简单。为什么这段代码没有抛出TaskCancelledException
?
static void Main()
{
var v = Task.Run(() =>
{
Thread.Sleep(1000);
return 10;
}, new CancellationTokenSource(500).Token).Result;
Console.WriteLine(v); // this outputs 10 - instead of throwing error.
Console.Read();
}
但是这个可以
static void Main()
{
var v = Task.Run(() =>
{
Thread.Sleep(1000);
return 10;
}, new CancellationToken(true).Token).Result;
Console.WriteLine(v); // this one throws
Console.Read();
}
托管线程取消:
取消是协作的,不会强加给侦听器。侦听器确定如何优雅地终止以响应取消请求。
您没有在Task.Run
方法中编写任何代码来访问CancellationToken
并实现取消-因此您有效地忽略了取消请求并运行到完成
取消正在运行的任务和计划运行的任务是不同的。
在调用Task之后。
当你使用Task.Run(…(CancellationToken)类重载的取消支持,在任务即将运行时检查取消令牌。如果此时取消令牌将IsCancellationRequested设置为true,则抛出TaskCanceledException类型的异常。
如果任务已经在运行,则任务的责任是调用ThrowIfCancellationRequested方法,或者只是抛出OperationCanceledException。
根据MSDN,这只是一个方便的方法,用于以下内容:
如果(token.IsCancellationRequested)throw new OperationCanceledException(token);
注意这两种情况下使用的不同类型的异常:
catch (TaskCanceledException ex)
{
// Task was canceled before running.
}
catch (OperationCanceledException ex)
{
// Task was canceled while running.
}
还要注意,TaskCanceledException
派生自OperationCanceledException
,因此对于OperationCanceledException
类型,您只能有一个catch
子句:
catch (OperationCanceledException ex)
{
if (ex is TaskCanceledException)
// Task was canceled before running.
// Task was canceled while running.
}
在代码的第一个变体中,您没有做任何事情来管理取消令牌。
例如,您没有检查token.IsCancellationRequested
是否返回true
(然后抛出异常)或从CancellationToken对象调用ThrowIfCancellationRequested()
。
此外,您使用的Task.Run
过载检查令牌是否已经取消,当任务即将启动时,您的代码声明令牌将在500毫秒后报告取消。因此,您的代码只是忽略了取消请求,这就是任务运行到完成的原因。
你应该这样做:
void Main()
{
var ct = new CancellationTokenSource(500).Token;
var v =
Task.Run(() =>
{
Thread.Sleep(1000);
ct.ThrowIfCancellationRequested();
return 10;
}, ct).Result;
Console.WriteLine(v); //now a TaskCanceledException is thrown.
Console.Read();
}
或this,不传递令牌,正如其他人已经注意到的:
void Main()
{
var ct = new CancellationTokenSource(500).Token;
ct.ThrowIfCancellationRequested();
var v =
Task.Run(() =>
{
Thread.Sleep(1000);
return 10;
}).Result;
Console.WriteLine(v); //now a TaskCanceledException is thrown.
Console.Read();
}
代码的第二个变体可以工作,因为您已经初始化了一个令牌,并将Canceled
状态设置为true。确实,如这里所述:
If canceled is true, both CanBeCanceled and IsCancellationRequested will be true
已经请求取消,然后异常TaskCanceledException
将立即抛出,而不实际启动任务。
另一个使用Task的实现。用token代替Thread.Sleep延迟。
static void Main(string[] args)
{
var task = GetValueWithTimeout(1000);
Console.WriteLine(task.Result);
Console.ReadLine();
}
static async Task<int> GetValueWithTimeout(int milliseconds)
{
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
cts.CancelAfter(milliseconds);
token.ThrowIfCancellationRequested();
var workerTask = Task.Run(async () =>
{
await Task.Delay(3500, token);
return 10;
}, token);
try
{
return await workerTask;
}
catch (OperationCanceledException )
{
return 0;
}
}