Task.WhenAny - 一项任务被取消

时间:2017-04-05 20:12:05

标签: c# .net async-await task-parallel-library task

在以下代码中:

        if (await Task.WhenAny(task, Task.Delay(100)) == task) {
            success = true;
        }
        else {
            details += "Timed out on SendGrid.";
            await task.ContinueWith(s =>
            {
                LogError(s.Exception);
            }, TaskContinuationOptions.OnlyOnFaulted);
        }

我偶尔会A task was cancelled致电await task.ContinueWith。我的目标是查看task是否在100毫秒内完成 - 如果没有,我想处理一些日志记录(这个特定任务有资源泄漏,所以我试图通过将其包装起来解决它超时)。这是从这里的指导中提取的:Debugging Task.WhenAny and Push Notifications

为什么会发生这种情况,我该怎么做才能防止抛出此异常?

3 个答案:

答案 0 :(得分:6)

当你的task无法及时完成(100毫秒)时会发生这种情况,但以后能够完成。你使用TaskContinuationOptions.OnlyOnFaulted继续运行,如果原始任务没有出现故障,这些任务就会被取消。 await ContinueWith await的结果,如果您的任务没有出现问题,您的续例将被取消,并且您有例外情况。

通常这种处理超时的方法没有多大意义,因为即使在达到超时之后,您仍然要等到原始任务完成,才能记录异常。我认为你必须在继续之前删除Task.WhenAny。然后代码将在超时时继续,但如果任务稍后失败 - 它将被记录。

您的代码中还有另一个问题 - await Task.WhenAny(task, Task.Delay(100)) == task 永远不会抛出。所以这个条件:

task

并不意味着成功,因为task.Status可能会出现问题。即使task.Exception表示您的任务已完成,也请务必检查WhenAnystatic class TaskExtensions { public static void Forget(this Task task) { // do nothing } } 。顺便说一句,您在问题中链接的答案提到了这一点。

更新:如果您不喜欢关于未等待电话的VS警告 - 您可以为此特定行禁用它,或使用这样的扩展方法:

task.ContinueWith(s => {
    Logger.Write(s.Exception);
}, TaskContinuationOptions.OnlyOnFaulted).Forget();

然后:

node_modules/.bin/karma

这样做没有任何害处(当然在这种特殊情况下),VS只是在每个可能等待的等待的电话上发出此警告。

答案 1 :(得分:0)

如果未运行任务延续,则会取消它们。您的任务继续将运行OnlyOnFaulted。如果没有错,则继续将被取消。

您的两个不涉及处理该特定异常的选择是要么不等待任何事情(并且不知道Task是否已经完成运行),要么等待原始任务。等待完成后,任务继续将在该点运行或取消。

如果你处理取消很好,那么只需根据需要捕获任务继续的TaskCancelledException(或者适当的AggregateException)并丢弃异常。

在我看来,对于这种特定情况(记录异常),最好的方法是再次等待原始任务并处理TaskCancelledException,不需要任务继续并且完全异步/等待兼容。

答案 2 :(得分:0)

正如@Evk已经提到的那样,你的Continuation被取消了。但我想补充一点,延续可以被视为配置的一部分,因此在Task生成时设置。请考虑以下事项:

var task = Task.Delay(500).ContinueWith(s =>
{
    LogError(s.Exception);
}, TaskContinuationOptions.OnlyOnFaulted);

if (await Task.WhenAny(task, Task.Delay(100)) == task) {
    success = true;
} else {
    details += "Timed out on SendGrid.";
}

在这种方法中,您的异常仍然会被记录,并且您的逻辑将继续只知道Task超时。如果除了超时之外,其他逻辑确实需要了解Exception,那么您需要再次await Task进行讨论。

为清晰起见而更新

原始代码(1)在此阶段,我们无法看到task的定义位置。

//task is undeclared in this snippet
if (await Task.WhenAny(task, Task.Delay(100)) == task) {
    success = true;
}
else {
    details += "Timed out on SendGrid.";
    await task.ContinueWith(s =>
    {
        LogError(s.Exception);
    }, TaskContinuationOptions.OnlyOnFaulted);
}

(2)让我们添加一个模拟任务,例如

var task = Task.Delay(500); //defines task as a Task that will complete in 500ms
if (await Task.WhenAny(task, Task.Delay(100)) == task) {
    success = true;
}
else {
    details += "Timed out on SendGrid.";
    await task.ContinueWith(s =>
    {
        LogError(s.Exception);
    }, TaskContinuationOptions.OnlyOnFaulted);
}

(3)接下来,当我们await task.ContinueWith时,我们允许抛出TaskCancelledException。如果删除await,则可以忽略Exception,但我们会收到一条未等待任务的警告。我们可以忽略该警告,或者我们可以认识到continuation可以被视为特定Task配置的一部分。有了这个,我们可以使用适当的选项配置我们创建任务的ContinuationTaskContinuationOptions.OnlyOnFaulted

//Add continuation configuration where task is created
var task = Task.Delay(500).ContinueWith(s =>
{
    LogError(s.Exception);
}, TaskContinuationOptions.OnlyOnFaulted);

if (await Task.WhenAny(task, Task.Delay(100)) == task) {
    success = true;
} else {
    details += "Timed out on SendGrid.";
    //removed continuation from here.
}