等待vs Task.Wait-死锁?


194

我不太明白之间的差别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
    }
}

哪里Get将陷入僵局。

是什么原因造成的?为什么在我使用阻塞等待而不是时这不会引起问题await Task.Delay


@Servy:有时间我会尽快回购。目前,它可以与之配合使用Task.Delay(1).Wait()
罗纳格2012年

2
Task.Delay(1).Wait()基本上和Thread.Sleep(1000)。在实际的生产代码中,这几乎是不合适的。
Servy

@ronag:您WaitAll正在导致僵局。有关更多详细信息,请参见答案中指向我的博客的链接。您应该await Task.WhenAll改用。
史蒂芬·克雷里

6
@ronag因为你有ConfigureAwait(false)一个单一的调用BarRos不会死锁,而是因为您有创建多个,然后等待所有的枚举,第一条将死锁第二。如果您await Task.WhenAll不等待所有任务,而又不阻塞ASP上下文,则将看到该方法正常返回。
Servy 2012年

2
@ronag您的另一种选择是.ConfigureAwait(false) 在树上一直添加直到被阻止为止,这样就不会尝试返回主上下文了。那会起作用。另一个选择是启动内部同步上下文。 链接。如果您将它放进去Task.WhenAllAsyncPump.Run它将有效地阻塞整个事情,而无需到ConfigureAwait任何地方,但这可能是一个过于复杂的解决方案。
Servy 2012年

Answers:


268

Waitawait-而概念上类似于-实际上是完全不同的。

Wait将同步阻止,直到任务完成。因此,当前线程实际上被阻塞,等待任务完成。通常,应使用“ async一直向下”;也就是说,不要阻塞async代码。在我的博客上,我详细介绍了异步代码中的阻塞如何导致死锁

await将异步等待,直到任务完成。这意味着当前方法已“暂停”(已捕获其状态),并且该方法向其调用者返回了未完成的任务。稍后,当await表达式完成时,将该方法的其余部分安排为继续。

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

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


4
我认为有一个误解,Wait可以解决await僵局。
罗纳格2012年

5
不,任务计划程序不会这样做。Wait阻塞线程,并且不能用于其他用途。
史蒂芬·克雷里

8
@ronag我的猜测是,您只是混淆了方法名,而死锁实际上是由阻塞代码引起的,并已与await代码一起工作。要么这与死锁无关,要么您误诊了问题。
Servy 2012年

3
@hexterminator:这是设计使然-它对于UI应用程序非常有效,但对于ASP.NET应用程序却势必会妨碍您的使用。ASP.NET Core已通过删除来解决此问题SynchronizationContext,因此ASP.NET Core请求中的阻止不再会出现死锁。
斯蒂芬·克莱里

1
在这里的msdn上,它说wait异步地在单独的线程上运行。我想念什么吗?msdn.microsoft.com/zh-CN/library/hh195051(v=vs.110).aspx
batmaci

6

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

一个await表达式不阻止它在其上执行线程。而是,它使编译器将async方法的其余部分注册为已完成任务的延续。控制然后返回到async方法的调用者。任务完成后,它将调用其继续,并执行async方法的将从中断处继续。

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

这篇文章也是不错的阅读。


3
“那在技术上不是那么错误吗?有人可以澄清一下吗?” -我可以澄清一下吗?您是否在问这个问题?(我只是想弄清楚您是要提问还是要回答)。如果您要问:作为一个单独的问题可能会更好;不可能在这里收集到新的答案作为答案
Marc Gravell

1
我已经回答了这个问题,并对我在这里的疑问提出了另一个问题stackoverflow.com/questions/53654006/…谢谢@MarcGravell。您能立即删除您对答案的删除票吗?
Ayushmati

“您能立即删除您对答案的删除表决吗?” -那不是我的;多亏♦,我的任何此类投票本会立即生效。但是,我认为这不能回答有关死锁行为的问题的关键点。
Marc Gravell

-2

其他答案未提供一些重要事实:

在CIL级别上,“异步等待”更为复杂,因此会花费内存和CPU时间。

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

在“异步等待”的情况下,我们没有此类任务的处理程序来取消或监视它。

使用“任务”比“异步等待”更为灵活。

任何同步功能都可以由异步包装。

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

我不明白为什么我必须忍受同步和异步方法的代码复制或使用黑客手段。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.