好吧,我的问题很简单。为什么这段代码不会抛出
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.Run方法后,任务只是被调度,可能还没有执行。
当您使用具有取消支持的 Task.Run(..., CancellationToken) 系列重载时,将在任务即将运行时检查取消令牌。如果此时取消标记的 IsCancellationRequested 设置为 true,则会抛出 TaskCanceledException 类型的异常。
如果任务已经在运行,则任务有责任调用 ThrowIfCancellationRequested 方法,或者只是抛出 OperationCanceledException。
根据MSDN,这只是以下的一种便捷方法:
if (令牌.IsCancellationRequested) 抛出新的OperationCanceledException(令牌);
注意这两种情况中使用的不同类型的异常:
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.
}
在代码的第一个变体中,您没有执行任何操作来管理取消令牌。
例如,您不会检查
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();
}
或者这个,不传递令牌,正如其他人已经指出的那样:
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 的令牌。事实上,正如这里所述:
如果取消是
,true
和CanBeCanceled
都将是IsCancellationRequested
true
已经请求取消,然后将立即抛出异常
TaskCanceledException
,而不实际启动任务。
另一种实现使用带有 token 的 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;
}
}