在任务执行委托中延迟取消支持的正确方法是什么?



我在MSDN或这里都没有看到任何关于如何完成此操作的具体提及。用例有些晦涩难懂,但我怀疑仍然有效。

var cancel = new CancellationTokenSource();
var task = Task.Factory.StartNew(() => { Task.Delay(1000, cancel.Token).Wait(); }, cancel.Token);
cancel.CancelAfter(100);
task.Wait();

上面的代码将在 100 毫秒后尝试取消包含分离子延迟任务的task,并等待task完成,这将生成AggregateException(由于取消)。这样做的问题是task出错而不是取消。这是预期的行为,因为延迟任务没有附加到父task,即使两者共享相同的取消令牌。

我的问题特别涉及如何将Task.Delay附加到已在运行的任务。如果您有权访问父任务,是否可以执行此操作?如果不可能,或者无法访问父任务实例就不可能,那么处理此方案的正确方法是什么?

我能想到的最好的解决方法是将延迟任务的Wait包装在 try/finally 块中,并明确尝试冒泡任务取消。

try { Task.Delay(1000, cancel.Token).Wait(); } finally { cancel.Token.ThrowIfCancellationRequested(); }

虽然有效,但感觉不太对劲,但我不确定是否有更好的方法来实现这一目标。期望的结果是,如果发生取消,父任务将转到Canceled而不是Faulted。因此,如果取消的起源发生在分离的子任务中,则父任务仍应转换为Canceled

注意:我故意在这里省略了异步/等待,只是因为它似乎不会改变问题或结果。如果不是这种情况,请提供一个例子。

因此,当一个OperationCanceledException被抛出并未被捕获并且其关联的CancellationToken被取消时,任务被视为已取消。

在您的情况下,引发的异常是包含TaskCanceledException(OperationCanceledException)的AggregateException,而不是直接包含TaskCanceledException

有一个简单的方法可以解决这个问题。您可以使用task.GatAwaiter().GetResult()而不是使用Task.Wait将任何异常包装在AggregateException包装器中同步阻塞。这就是awaitasync-await中使用的。它抛出原始异常,如果有多个异常,它会抛出第一个:

var cancel = new CancellationTokenSource();
var task = Task.Factory.StartNew(() => { Task.Delay(1000, cancel.Token).GetAwaiter().GetResult(); }, cancel.Token);
cancel.CancelAfter(100);
task.Wait();

如果您使用async-await则不会遇到此问题,因为与Task.Wait不同,它会抛出TaskCanceledException本身:

var cancel = new CancellationTokenSource();
var task = Task.Run(() => Task.Delay(1000, cancel.Token), cancel.Token);
cancel.CancelAfter(100);
task.Wait();

我认为这只是一个例子。实际的生产代码不应与此类似,因为

异步操作上同步阻塞,而异步操作又在异步操作上同步阻塞。

相关内容

最新更新