await vs Task.Wait - 死锁?

ron*_*nag 169 c# deadlock task-parallel-library async-await

我不太明白之间的差别Task.Waitawait.

我在ASP.NET WebAPI服务中有类似于以下函数:

public class TestController : ApiController
{
    public static async Task<string> Foo()
    {
        await Task.Delay(1).ConfigureAwait(false);
        return "";
    }

    public async static Task<string> Bar()
    {
        return await Foo();
    }

    public async static Task<string> Ros()
    {
        return await Bar();
    }

    // GET api/test
    public IEnumerable<string> Get()
    {
        Task.WaitAll(Enumerable.Range(0, 10).Select(x => Ros()).ToArray());

        return new string[] { "value1", "value2" }; // This will never execute
    }
}
Run Code Online (Sandbox Code Playgroud)

哪里Get会僵局.

什么可能导致这个?当我使用阻塞等待而不是await Task.Delay?时,为什么这不会导致问题?

Ste*_*ary 243

Waitawait- 虽然在概念上类似 - 实际上是完全不同的.

Wait将同步阻止,直到任务完成.因此,当前线程被字面上阻止等待任务完成.作为一般规则,你应该使用" async一直向下"; 也就是说,不要阻止async代码.在我的博客上,我详细介绍了异步代码中的阻塞如何导致死锁.

await将异步等待任务完成.这意味着当前方法被"暂停"(捕获其状态),并且该方法将不完整的任务返回给其调用者.稍后,当await表达式完成时,方法的其余部分将被安排为延续.

您还提到了一个"协作块",我假设您的意思是您正在Wait执行的任务可能在等待的线程上执行.有些情况会发生这种情况,但这是一种优化.在许多情况下,它不会发生,例如,如果任务是针对另一个调度程序,或者它是否已经启动,或者它是否是非代码任务(例如在您的代码示例中:由于没有代码,Wait因此无法执行Delay内联任务)为了它).

您可能会发现我的async/ await介绍很有帮助.

  • @ronag我的猜测是你刚刚将你的方法名称混淆了,你的死锁实际上是由阻塞代码引起的,并使用`await`代码.无论是那个,还是僵局都与之无关,你错误地诊断出了这个问题. (8认同)
  • 不,任务调度程序不会这样做.`Wait`阻塞线程,它不能用于其他事情. (5认同)
  • 我认为有一个误解,"等待"工作得很好`等待'死锁. (4认同)
  • @hexterminator:这是设计的 - 它适用于UI应用程序,但确实会妨碍ASP.NET应用程序.ASP.NET Core通过删除`SynchronizationContext`来解决这个问题,因此在ASP.NET Core请求中阻塞不再是死锁. (3认同)
  • 很明显:是的,如果我用 `Task.Delay(1).Wait()` 替换我的 `await Task.Delay(1)`,服务工作正常,否则会死锁。 (2认同)
  • 那么,如果我们不能使用异步入口点,我们该如何解决这个问题……这似乎很常见? (2认同)
  • @JoePhillips:请参阅我的[brownfield 异步文章](https://msdn.microsoft.com/en-us/magazine/mt238404.aspx) 中的技巧。 (2认同)
  • @StephenCleary 那太迷幻了。我来这里是因为我无法理解 C# Cookbook 中的并发性,您首先提到了死锁,过了一段时间我意识到您是作者。 (2认同)
  • @user1785960:“`Wait` 在调用期间阻塞线程,并且当阻塞发生时,该线程不能用于其他事情。” - 怎么样? (2认同)

Ayu*_*ati 13

根据我从不同来源阅读的内容:

一个await表达式不阻止它在其上执行线程。相反,它会导致编译器将async方法的其余部分注册为等待任务的延续。然后控制权返回给async方法的调用者。当任务完成时,它调用它的继续,并且async方法的执行从它停止的地方恢复。

要等待单个task完成,您可以调用其Task.Wait方法。对该Wait方法的调用会阻塞调用线程,直到单个类实例完成执行。无参数Wait()方法用于无条件等待,直到任务完成。任务通过调用Thread.Sleep休眠两秒的方法来模拟工作。

这篇文章也很好读。

  • “那这在技术上是不是不正确?有人可以澄清一下吗?” - 我可以澄清一下吗?你这是在问这个问题吗?(我只是想弄清楚你是在问还是在回答)。如果你问:作为一个单独的问题可能会更好;不太可能在这里收集新的回复作为答案 (3认同)

use*_*960 7

其他答案中没有给出一些重要的事实:

async/await在 CIL 级别更复杂,因此会消耗内存和 CPU 时间。

如果等待时间不可接受,则可以取消任何任务。

如果async/await我们没有此类任务的处理程序来取消或监视它。

使用 Task 比async/await.

任何sync功能都可以通过async.

public async Task<ActionResult> DoAsync(long id) 
{ 
    return await Task.Run(() => { return DoSync(id); } ); 
} 
Run Code Online (Sandbox Code Playgroud)

async/await产生许多问题。我们不知道await在没有运行时和上下文调试的情况下是否会到达语句。如果第一次等待没有到达,一切都会被阻塞。有时即使await看似已达到,但一切仍然被阻止:

https://github.com/dotnet/runtime/issues/36063

我不明白为什么我必须忍受方法syncasync方法的代码重复或使用黑客。

结论:手动创建任务并控制它们要好得多。任务处理程序提供更多控制。我们可以监控任务并管理它们:

https://github.com/lsmolinski/MonitoredQueueBackgroundWorkItem

对不起我的英语不好。