好的,我的问题非常简单。为什么这段代码不会抛出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();
}
答案 0 :(得分:32)
Cancellation in Managed Threads:
取消是合作的,不会强制听众。侦听器确定如何优雅地终止以响应取消请求。
您没有在Task.Run
方法中编写任何代码来访问CancellationToken
并实施取消 - 因此您实际上忽略了取消请求并且已完成。
答案 1 :(得分:18)
取消正在运行的任务和计划运行的任务有所不同。
在调用Task.Run方法之后,该任务仅被安排,可能尚未执行。
当您使用具有取消支持的Task.Run(...,CancellationToken)重载系列时,将在任务即将运行时检查取消令牌。如果取消令牌此时将IsCancellationRequested设置为true,则抛出TaskCanceledException类型的异常。
如果任务已在运行,则任务负责调用ThrowIfCancellationRequested方法,或者只是抛出OperationCanceledException。
根据MSDN,它只是以下方便的方法:
if(token.IsCancellationRequested) 抛出新的OperationCanceledException(token);
在这两种情况下不使用不同类型的异常:
catch (TaskCanceledException ex)
{
// Task was canceled before running.
}
catch (OperationCanceledException ex)
{
// Task was canceled while running.
}
另请注意,TaskCanceledException
来自OperationCanceledException
,因此您只能为catch
类型设置一个OperationCanceledException
子句:
catch (OperationCanceledException ex)
{
if (ex is TaskCanceledException)
// Task was canceled before running.
// Task was canceled while running.
}
答案 2 :(得分:16)
我认为因为你没有从CancellationToken对象调用方法ThrowIfCancellationRequested()
。
以这种方式,您忽略了取消任务的请求。
你应该这样做:
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();
}
代码的第二个变体有效,因为您已经初始化了Canceled
状态设置为true的标记。实际上,正如所述here:
If canceled is true, both CanBeCanceled and IsCancellationRequested will be true
已经请求取消,然后会立即抛出异常TaskCanceledException
,而不会实际启动任务。
答案 3 :(得分:1)
另一个使用Task.Delay和令牌的实现,而不是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;
}
}