1

我有一个框架,它创建一个 CancellationTokenSource,配置 CancelAfter,然后调用一个异步方法并传递令牌。然后异步方法生成许多任务,将取消令牌传递给每个任务,然后等待任务集合。这些任务每个都包含通过轮询 IsCancellationRequested 优雅地取消的逻辑。

我的问题是,如果我将 CancellationToken 传递给 Task.Run(),则会引发包含 TaskCanceledException 的 AggregateException。这可以防止任务正常取消。

为了解决这个问题,我无法将 CancelationToken 传递给 Task.Run,​​但是我不确定我会失去什么。例如,我喜欢这样的想法,即如果我的任务挂起并且无法执行优雅的取消,则此异常将强制它关闭。我在想我可以用两个 CancelationTokens 来处理这个问题,一个“优雅”,另一个“强制”。但是,我不喜欢这种解决方案。

这是一些代表我上面描述的伪代码..

public async Task Main()
{
    CancellationTokenSource cts = new CancellationTokenSource();
    cts.CancelAfter(30000);
    await  this.Run(cts.Token);
}

public async Task Run(CancellationToken cancelationToken)
{
    HashSet<Task> tasks = new HashSet<Task>();
    foreach (var work in this.GetWorkNotPictured)
    {
        // Here is where I could pass the Token, 
        //   however If I do I cannot cancel gracefully
        //   My dilemma here is by not passing I lose the ability to force
        //   down the thread (via exception) if         
        //   it's hung for whatever reason
        tasks.Add(Task.Run(() => this.DoWork(work, cancelationToken))
    }

    await Task.WhenAll(tasks);

    // Clean up regardless of if we canceled
    this.CleanUpAfterWork();

    // It is now safe to throw as we have gracefully canceled
    cancelationToken.ThrowIfCancellationRequested();
}

public static void DoWork(work, cancelationToken)
{
    while (work.IsWorking)
    {
        if (cancelationToken.IsCancellationRequested)
          return // cancel gracefully
        work.DoNextWork();
    }
}
4

2 回答 2

4

我建议您遵循抛出异常而不是仅仅返回的标准取消模式:

public static void DoWork(work, cancellationToken)
{
  while (work.IsWorking)
  {
    cancellationToken.ThrowIfCancellationRequested();
    work.DoNextWork();
  }
}

如果您有清理工作要做,那finally就是(或者using,如果您可以通过这种方式重构):

public async Task Run(CancellationToken cancellationToken)
{
  HashSet<Task> tasks = new HashSet<Task>();
  foreach (var work in this.GetWorkNotPictured)
  {
    tasks.Add(Task.Run(() => this.DoWork(work, cancellationToken))
  }

  try
  {
    await Task.WhenAll(tasks);
  }
  finally
  {
    this.CleanUpAfterWork();
  }
}
于 2015-06-15T15:17:28.837 回答
0

除了将其传递给执行工作的方法之外,还提供CancellationTokento Task.Run。当你这样做时Task.Run可以看到抛出的异常是由CancellationToken它引起的,并将标记Task为已取消。

tasks.Add(Task.Run(() => this.DoWork(work, cancelationToken),
    cancelationToken));

完成此操作后,您可以确保DoWork在取消令牌时抛出,而不是检查IsCancellationRequested以尝试通过标记为“成功完成”来结束。

于 2015-06-15T15:10:02.327 回答