在Task.Run中使用CancellationToken超时不起作用
好的,我的问题非常简单。 为什么这段代码不会抛出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
并实现取消 – 因此您实际上忽略了取消请求并运行完成。
我想因为你没有从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的情况下初始化了一个令牌。 的确,如上所述:
If canceled is true, both CanBeCanceled and IsCancellationRequested will be true
已经请求取消,然后将立即抛出exceptionTaskCanceledException
,而不实际启动任务。
取消正在运行的任务和计划运行的任务有所不同。
在调用Task.Run方法之后,该任务仅被调度,并且可能尚未执行。
当您使用具有取消支持的Task.Run(…,CancellationToken)重载系列时,将在任务即将运行时检查取消令牌。 如果取消令牌此时将IsCancellationRequested设置为true,则抛出TaskCanceledException类型的exception。
如果任务已在运行,则任务负责调用ThrowIfCancellationRequested方法,或者只是抛出OperationCanceledException。
根据MSDN,它只是一种方便的方法:
if(token.IsCancellationRequested)抛出新的OperationCanceledException(token);
不是在这两种情况下使用的不同类型的exception:
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. }
使用带有令牌的Task.Delay的另一个实现,而不是Thread.Sleep。
static void Main(string[] args) { var task = GetValueWithTimeout(1000); Console.WriteLine(task.Result); Console.ReadLine(); } static async Task 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; } }