Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/variables/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何使用CancellationToken安全地取消任务并等待任务。Whalll_C#_Multithreading_Asynchronous_Async Await_Cancellationtokensource - Fatal编程技术网

C# 如何使用CancellationToken安全地取消任务并等待任务。Whalll

C# 如何使用CancellationToken安全地取消任务并等待任务。Whalll,c#,multithreading,asynchronous,async-await,cancellationtokensource,C#,Multithreading,Asynchronous,Async Await,Cancellationtokensource,我有一个框架,它创建一个CancellationTokenSource,配置CancelAfter,然后调用一个异步方法并传递令牌。然后,异步方法生成许多任务,将取消令牌传递给每个任务,然后等待任务集合。这些任务都包含通过轮询IsCancellationRequested来正常取消的逻辑 我的问题是,如果我将CancellationToken传递到Task.Run,则会引发包含TaskCanceledException的AggregateException。这将防止任务正常取消 为了避免这个问题

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

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

为了避免这个问题,我不能将CancelationToken传递到Task.Run,但是我不确定会丢失什么。例如,我喜欢这样的想法:如果我的任务挂起并且无法执行优雅的取消,那么这个异常将迫使它停止。我想我可以用两个CancelationToken来处理这个问题,一个是“优雅”,另一个是“力量”。然而,我不喜欢这个解决方案

下面是一些psudo代码,表示我上面描述的内容

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();
    }
}
向Task.Run提供CancellationToken,并将其传递给执行工作的方法。执行此任务时。Run可以看到引发的异常是由给定的CancellationToken引起的,并将任务标记为已取消

tasks.Add(Task.Run(() => this.DoWork(work, cancelationToken),
    cancelationToken));
完成此操作后,您可以确保DoWork在令牌被取消时抛出,而不是检查IsCancellationRequested以尝试通过标记为已成功结束来结束。

将CancellationToken提供给Task。除了将其传递给执行此工作的方法之外,还可以运行。执行此任务时。Run可以看到引发的异常是由给定的CancellationToken引起的,并将任务标记为已取消

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

完成此操作后,您可以确保DoWork在令牌被取消时抛出,而不是检查IsCancellationRequested以尝试通过标记为已成功结束来结束。我建议您遵循标准的取消模式,即抛出异常,而不仅仅返回:

public static void DoWork(work, cancellationToken)
{
  while (work.IsWorking)
  {
    cancellationToken.ThrowIfCancellationRequested();
    work.DoNextWork();
  }
}
如果您有清理工作要做,这就是最终的用途,如果您可以这样重构:

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();
  }
}

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

public static void DoWork(work, cancellationToken)
{
  while (work.IsWorking)
  {
    cancellationToken.ThrowIfCancellationRequested();
    work.DoNextWork();
  }
}
如果您有清理工作要做,这就是最终的用途,如果您可以这样重构:

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();
  }
}

谢谢你的回复!我同意这种方法,清理是我一直想做的事情,所以在最后一个阶段进行清理是合适的。2个问题:1。我是否需要检查cancellationToken.ThrowIfCancellationRequested?这不就是任务抛出的吗。在传递令牌后运行吗?2.通过重构使用using,您是指在处理完成工作的类时执行清理吗?1否,传递给Task.Run的取消令牌仅在委托开始执行之前观察到;我在后面的文章中对此进行了详细描述。2是。@StephenCleary,如何取消子线程特定任务并继续主线程与子任务?我在这里问了个问题谢谢你的回答!我同意这种方法,清理是我一直想做的事情,所以在最后一个阶段进行清理是合适的。2个问题:1。我是否需要检查cancellationToken.ThrowIfCancellationRequested?这不就是任务抛出的吗。在传递令牌后运行吗?2.通过重构使用using,您是指在处理完成工作的类时执行清理吗?1否,传递给Task.Run的取消令牌仅在委托开始执行之前观察到;我在后面的文章中对此进行了详细描述。2是。@StephenCleary,如何取消子线程特定任务并继续主线程与子任务?我在这里问了一个问题,就是我在将令牌传递给任务时遇到的问题。Run会抛出TaskCanceledException,并阻止“CleanUpAfterWork”逻辑。@thaynes如果您想在此时正常继续,您可以捕获异常,或者您使用finally来确保即使工作抛出,清理也会运行。@thaynes关于这一点的任何内容都不是针对异步编程的,真的。即使出现异常,您也应该始终使用finally/using来确保清理运行。我在将令牌传递给Task时遇到的问题是,运行会引发TaskCanceledException并阻止“CleanUpAfterWork”逻辑。@Thayes然后,如果您想在此时正常继续,您可以捕获异常,或者您使用finally来确保即使工作抛出,清理也会运行。@thaynes关于这一点的任何内容都不是针对异步编程的,真的。即使出现异常,也应始终使用finally/using来确保清理运行。