【问题标题】:Task.Run vs Task.Factory.StartNew - Expected Deadlock is not happeningTask.Run vs Task.Factory.StartNew - 没有发生预期的死锁
【发布时间】:2020-09-20 13:10:37
【问题描述】:

我了解了 Task.Run 和 Task.Factory.StartNew 的区别。

Task.Run(() => {});

应该等价于

Task.Factory.StartNew(() => {}, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

但在我的代码中,我预计由于 Task.Factory.StartNew 不会发生死锁:

private Task backgroundTask;

private async Task DoSomethingAsync()
{
   // this should deadlock
   await this.backgroundTask.ConfigureAwait(false);
   throw new Exception();
}

private async Task Test()
{
   this.backgroundTask = Task.Factory.StartNew(async () =>
      {
         await this.DoSomethingAsync().ConfigureAwait(false);
      }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

   // just wait here for testing/debugging
   await Task.Delay(10000).ConfigureAwait(false);
   // if no deadlock, this should throw
   await this.backgroundTask.ConfigureAwait(false);
}

但这不是死锁。 DoSomethingAsync 中的异常被抛出但从未被捕获。 在Task.Delay之后等待Task也不会抛出,因为它是RanToCompletion。

当使用 Task.Run 时,它会像预期的那样死锁:

private Task backgroundTask;

private async Task DoSomethingAsync()
{
   // this is deadlocking
   await this.backgroundTask.ConfigureAwait(false);
   throw new Exception();
}

private async Task Test()
{
   this.backgroundTask= Task.Run(async () =>
      {
         await this.DoSomethingAsync().ConfigureAwait(false);
      });

   // just wait here for testing/debugging
   await Task.Delay(10000).ConfigureAwait(false);
   // never reached because of deadlock
   await this.backgroundTask.ConfigureAwait(false);
}

谁能解释这种行为?

【问题讨论】:

标签: c# async-await task deadlock


【解决方案1】:

Task.Factory.StartNew 方法与异步委托一起使用时会返回一个嵌套任务:Task<Task>。您将此嵌套任务分配给Task 类型的变量,这是允许的,因为类Task<TResult> 派生自类Task。然后发生的事情是您失去了对内部任务的引用,因此您无法等待它。当您await this.backgroundTask 时,您正在等待外部Task<Task>,但您无法获得等待操作的结果,即Task、内部Taskawait。内心的任务变成了即发即弃的任务。

【讨论】:

  • 这可以解释这种行为。但是Task(外层任务)RanToCompletion是什么时候,是什么意思呢?
  • @AlteGurke 当委托到达第一个await 时,外部任务完成,这是因为对委托的调用返回代表异步等待的任务对象。
  • @AlteGurke 当Task<Task>完成时,表示内部任务刚刚创建。通常创建一个任务并没有太多的工作要做,所以让一个任务创建另一个任务没有多大意义。这就是为什么 Task.Run 方法在提供异步委托时将两个任务合二为一,并返回一个“代理”任务,该任务在外部和内部任务都完成时完成。但是您可能会遇到一些使用Task<Task> 有意义的罕见情况,例如当您想要使用two awaiting points 执行任务时。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-31
  • 2016-11-20
  • 1970-01-01
  • 1970-01-01
  • 2017-08-15
  • 2020-07-16
相关资源
最近更新 更多