【问题标题】:What is the best way to end a task to prevent run-away结束任务以防止失控的最佳方法是什么
【发布时间】:2015-08-08 05:46:12
【问题描述】:

我创建了下面的函数,当取消或超时发生时,它将等待所有任务完成或引发异常。

public static async Task WhenAll(
    IEnumerable<Task> tasks, 
    CancellationToken cancellationToken,
    int millisecondsTimeOut)
{
    Task timeoutTask = Task.Delay(millisecondsTimeOut, cancellationToken);
    Task completedTask = await Task.WhenAny(
        Task.WhenAll(tasks), 
        timeoutTask
    );
    if (completedTask == timeoutTask)
    {
        throw new TimeoutException();
    }
}

如果所有tasks 在长时间超时之前完成(即millisecondsTimeOut = 60,000),即使在函数返回后,timeoutTask 是否会一直停留到 60 秒?如果是,解决失控问题的最佳方法是什么?

【问题讨论】:

  • 它会闲逛,但真的很轻。只要你没有数百个这样的东西,如果你让它运行到完成,你可能不会看到任何区别。但是,您可以在您的方法中创建一个CancellationTokenSource,将其传递给CancellationTokenTask.Delay。在您的方法结束时,只需取消将终止延迟的令牌。
  • 好吧,如果它每秒被调用 100 次,那么这可能是非常重要的开销。您冒着内存使用量爆炸式增长的风险。这是定时内存泄漏。测试时不会被发现。它使应用程序不可靠。
  • 这个函数的目的是什么?作为一种信号机制,还是在工作线程执行时暂停主(或监控)线程?如果你只是想杀死所有的工作线程,那么你有CancellationTokenSource,所以只需使用它......

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


【解决方案1】:

是的,timeoutTask 会一直存在,直到超时结束(或 CancellationToken 被取消)。

您可以通过传入一个不同的CancellationToken 来修复它,您从使用CancellationTokenSource.CreateLinkedTokenSource 创建的新CancellationTokenSource 获得并在最后取消。您还应该等待已完成的任务,否则您并没有真正观察到异常(或取消):

public static async Task WhenAll(
    IEnumerable<Task> tasks,
    CancellationToken cancellationToken,
    int millisecondsTimeOut)
{
    var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
    var timeoutTask = Task.Delay(millisecondsTimeOut, cancellationTokenSource.Token);
    var completedTask = await Task.WhenAny(Task.WhenAll(tasks), timeoutTask);
    if (completedTask == timeoutTask)
    {
        throw new TimeoutException();
    }

    cancellationTokenSource.Cancel();
    await completedTask;
}

但是,如果您不需要区分 TimeoutExceptionTaskCancelledException,我认为有一种更简单的方法可以实现您想要的。您只需添加一个在 CancellationToken 被取消或超时结束时被取消的延续:

public static Task WhenAll(
    IEnumerable<Task> tasks,
    CancellationToken cancellationToken,
    int millisecondsTimeOut)
{
    var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
    cancellationTokenSource.CancelAfter(millisecondsTimeOut);

    return Task.WhenAll(tasks).ContinueWith(
        _ => _.GetAwaiter().GetResult(), 
        cancellationTokenSource.Token,
        TaskContinuationOptions.ExecuteSynchronously, 
        TaskScheduler.Default);
}

【讨论】:

  • 您能详细说明一下为什么在末尾添加await completedTask 吗?我认为Task.WhenAll 会等待所有任务完成、取消或出现异常。如果函数由于超时而返回,调用者将负责取消/结束任务或执行其他工作。
  • 因为Task.WhenAny 有签名Task&lt;Task&gt;,第二个等待是观察内部任务的结果,所以如果它确实抛出了异常,它的异常可以传播到调用堆栈。跨度>
  • @Tony Task.WhenAll 如果其中一项任务引发异常,则会引发异常...Task.WhenAny 不会。如果您希望调用者知道发生了异常,您需要等待已完成的任务并重新抛出异常。
猜你喜欢
  • 2012-09-11
  • 2013-03-25
  • 2016-12-19
  • 1970-01-01
  • 2011-01-07
  • 1970-01-01
  • 2011-01-08
  • 2015-12-06
  • 1970-01-01
相关资源
最近更新 更多