【问题标题】:Awaiting an empty Task spins forever (await new Task(() => { }))等待一个空任务永远旋转(等待新任务(()=> {}))
【发布时间】:2015-02-02 14:03:24
【问题描述】:

我正在努力理解这段代码:

 [TestFixture]
 public class ExampleTest
 {
    [Test]
    public void Example()
    {
        AwaitEmptyTask().Wait();
    }

    public async Task AwaitEmptyTask()
    {
        await new Task(() => { });
    }
 }

方法Example 永远不会结束并且永远阻塞。 为什么?

修复(来自Stubbing Task returning method in async unit test)是将await new Task( () => {}) 替换为return Task.FromResult<object>(null);,但同样,为什么需要这样做?

我知道有很多与此类似的问题,但我所见过的似乎都无法解释为什么会发生这种情况:

【问题讨论】:

  • 我建议你使用Task.Factory.StartNew不要忘记开始一个任务
  • @AlexJoukovsky 我建议他不要使用Task.Factory.StartNew,而是使用Task.Run。 There are issues with using StartNew.

标签: c# .net asynchronous async-await task


【解决方案1】:

即使你把它改成

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

它可能永远不会完成。当您点击此行时,程序会创建一个继续任务,该任务将在任务完成时继续。任务为空的事实是无关紧要的。此时,控制流回到

AwaitEmptyTask().Wait();

等待任务完成并阻塞当前线程。

当任务完成时,它会尝试在当前线程上继续。但是不能,因为线程被 Wait() 调用阻塞了。

将等待的行改为

await Task.Run(() => { }).ConfigureAwait(false);

这是一个常见的死锁问题。不要混合 Wait() 和等待。见http://msdn.microsoft.com/en-us/magazine/jj991977.aspx

【讨论】:

  • 这假设有一个单线程同步上下文。这可能不是这里的情况,但正是由于这个原因,它确实是一个糟糕的设计。
  • @Servy - 我不确定我是否关注你。你能详细说明一下吗?
  • 如果SynchronizationContext.Current 为空或默认同步上下文,则调用者正在等待这一事实不会阻止继续运行。他需要从例如带有消息循环的桌面应用程序的 UI 线程调用Example 才能发生这种情况。作为一种测试方法,情况可能并非如此;它可能只是一个没有(或默认)同步上下文的控制台应用程序。
  • @PhilipPittle 它将继续任务配置为在任何可用线程上继续。默认情况下,它将尝试在同一个线程上继续,因此出现上述问题。
  • @user1158174 这不太对。它告诉它使用默认同步上下文,而不是使用当前同步上下文进行继续。当前同步上下文可能涉及也可能不涉及处理所有已发布操作的当前线程。在这种情况下,它可能不会,因为它可能使用默认同步上下文调用。默认上下文不允许任何线程处理它,而是(在其当前实现中)将请求发送到线程池。
【解决方案2】:

您需要致电Start() 处理您的任务。

否则,它永远不会完成。

【讨论】:

  • await 关键字不是启动任务吗?
  • @PhilipPittle 不,它只等待任务完成。
  • @SLaks - 麻烦您更新一下。我到底应该在哪里打电话给Start()?在Example() 或AwaitEmptyTask()?在Example() 中调用AwaitEmptyTask().Start(); 会引发System.InvalidOperationException : Start may not be called on a promise-style task.
  • @PhilipPittle 只是不要。您不想在不首先启动任务的情况下创建任务。调用Task.Run 而不是使用构造函数。
  • @PhilipPittle:你需要将Task存储在一个变量中,调用Start(),然后await它。但是,您应该致电 Task.Run()。
【解决方案3】:

您正在创建一个任务并且从未启动它,因此它永远不会完成。

您应该使用Task.Run 创建要立即开始执行的任务,而不是使用Task 构造函数。

【讨论】:

  • await 关键字不是启动任务吗?
  • 它为任务附加了一个延续。而已。您可以从您自己的代码中清楚地看出它没有启动任务。如果是这样,那么您的代码实际上会完成。
  • 如果它只附加一个continuation 任何想法为什么运行时不抛出异常让你知道你是await - 正在执行一个未启动的任务?是否存在需要这种行为的用例?
  • @PhilipPittle 没有理由不能为未开始的任务添加延续。它无法知道您从未计划开始这项任务。它假定如果您要向未启动的任务添加延续,那么您最终会启动它,如果您希望延续在某个时间点运行。通过使用构造函数,您会特意说:“我还不想启动它,但别担心,到时候我最终会启动您的”。如果这不是您想要发生的,那么您不应该使用构造函数。
  • @PhilipPittle 他们做的事情完全不同。您是在尝试创建一个结果为已知值的已完成任务,还是尝试在线程池线程中执行方法?你想做什么决定了你想调用哪个方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-04-24
  • 1970-01-01
  • 2016-06-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多