await vs Task.Wait - Deadlock?
我不太明白Task.Wait和await之间的区别。
我在ASP.NET WebAPI服务中有类似于以下函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| 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时,为什么这不会导致问题?
-
你的代码不会编译;您的方法被列为同时包含void和Task作为返回类型。请提供可用于演示此问题的可编译代码。哦,当我修复该错误并运行代码时,我不会遇到任何死锁(我预计会出现这种情况)。
-
@Servy:我会尽快回来买回购。现在它适用于Task.Delay(1).Wait(),这已经足够了。
-
Task.Delay(1).Wait()基本上与Thread.Sleep(1000)完全相同。在实际的生产代码中,它很少适用。
-
@Servy:添加了repo示例。
-
@ronag:您的WaitAll导致死锁。有关更多详细信息,请参阅我的答案中指向我博客的链接。您应该使用await Task.WhenAll。
-
您的代码是死锁的,因为您阻塞了异步操作的结果,就像Stephens回答中的链接所示。你需要await才能使它工作。同样,你可以一直阻止它,它会工作。在你阻止实际上从不实际await的示例中,你一直阻塞,所以它不会死锁(它也会在异步操作期间阻塞线程而不是让线程自由)。
-
我不太明白我正在做ConfigureAwait(false),如你所指的链接所示。我不能一直使用异步,因为我需要在实际代码中进行大量的代码更改。
-
@ronag因为你有ConfigureAwait(false)对Bar或Ros的单个调用不会死锁,但是因为你有一个可以创建多个的枚举,然后等待所有这些,第一个栏会使第二个死锁。如果您await Task.WhenAll而不是等待所有任务,那么您不会阻止ASP上下文,您将看到方法正常返回。
-
@Servy:啊!现在我明白了。谢谢。
-
@ronag你的另一个选择就是在树上一直添加.ConfigureAwait(false)直到你阻塞,这样就没有任何东西试图回到主要的上下文;那会有用。另一种选择是启动内部同步上下文。链接。如果你将Task.WhenAll放在AsyncPump.Run中,它将有效地阻止整个事情,而不需要ConfigureAwait,但这可能是一个过于复杂的解决方案。
-
可能重复什么是Task.Start / Wait和Async / Await之间的区别?
Wait和await - 虽然概念上相似 - 实际上完全不同。
Wait将同步阻止,直到任务完成。因此,当前线程被字面上阻止等待任务完成。作为一般规则,你应该使用"async一直向下";也就是说,不要阻止async代码。在我的博客上,我详细介绍了异步代码中的阻塞如何导致死锁。
await将异步等待任务完成。这意味着当前方法被"暂停"(捕获其状态),并且该方法将不完整的任务返回给其调用者。稍后,当await表达式完成时,方法的其余部分将被安排为延续。
您还提到了一个"协作块",我假设您的意思是您正在执行的任务可能在等待的线程上执行。有些情况会发生这种情况,但这是一种优化。在许多情况下它不会发生,例如,如果任务是针对另一个调度程序,或者它是否已经启动,或者它是否是非代码任务(例如在您的代码示例中:Wait无法执行Delay任务内联,因为它没有代码)。
您可能会发现我的async / await介绍很有帮助。
-
我认为有一个误解,Wait工作正常await死锁。
-
你绝对确定吗?
-
我不是说我等待的任务可能在等待线程上执行。我的意思是,等待任务调度程序将在调用Wait的线程上执行其他任务。
-
显然:是的,如果我用Task.Delay(1).Wait()替换await Task.Delay(1),服务工作正常,否则会死锁。
-
不,任务调度程序不会这样做。 Wait阻塞线程,不能用于其他事情。
-
显然:有趣,我不会想到,我有点惊讶。在PPL / Concrt中,任何阻塞操作都是合作的,正如我所描述的那样。
-
我无法让你的基于await的代码在我的机器上死锁。你能提供回购吗?
-
有很多代码可以创建一个repo。我会尝试提供一个回购,虽然这将需要等待一段时间,直到我有时间。它现在正常工作,现在已经足够好了,尽管我对await的问题非常好奇。
-
@ronag我的猜测是你刚刚将你的方法名称混淆了,你的死锁实际上是由阻塞代码引起的,并使用了await代码。无论是那个,还是僵局都与之无关,你错误地诊断出了这个问题。
-
添加了回购样本。
-
当.NET团队阻止上下文并允许在同一个上下文中安排延续时,或者这是设计时,.NET团队是否在这个死锁问题上失败了?
-
@hexterminator:这是设计的 - 它适用于UI应用程序,但确实会妨碍ASP.NET应用程序。 ASP.NET Core通过删除SynchronizationContext解决了这个问题,因此在ASP.NET Core请求中阻塞不再是死锁。
-
@StephenCleary我们可以通过使用等待Task.WaitAll()而不是使用Task.WaitAll()来克服死锁吗?
-
@Afshin_Zavvar:阻止异步代码必须允许死锁或重入。没有干净的解决方案。
-
在msdn这里,它说等待在异步线程上运行。我错过了什么吗? msdn.microsoft.com/en-us/library/hh195051(v=vs.110).aspx
-
@batmaci:那就是谈论Task.Run,而不是async / await。
-
@StephenCleary所以我想知道,就像在msdn文章中一样。这段代码会在同步包装器中运行async吗? var t = Task.Run(()=> {Console.WriteLine("任务线程ID:{0}",Thread.CurrentThread.ManagedThreadId);}); t.Wait();
-
@batmaci:如果用"这个代码",你的意思是这个问题和答案中的代码,那么不,它不是异步同步。如果通过"此代码",您的意思是您对此答案的评论中发布的代码,那么是的,它会运行async-over-sync。
-
@StephenCleary是我粘贴的代码,我想问一下。谢谢你说清楚。
-
那么如果我们不能使用异步入口点,我们如何解决这个问题呢?这似乎很常见?
-
@JoePhillips:看看我的棕色异形文章中的hacks。
基于我从不同来源阅读的内容:
await表达式不会阻止它正在执行的线程。相反,它会导致编译器将其余的异步方法注册为等待任务的延续。然后,Control返回到异步方法的调用者。当任务完成时,它会调用它的继续,异步方法的执行从它停止的地方恢复。
要等待单个任务完成,可以调用其Task.Wait方法。对Wait方法的调用会阻塞调用线程,直到单个类实例完成执行。无参数Wait()方法用于无条件地等待,直到任务完成。该任务通过调用Thread.Sleep方法休眠两秒来模拟工作。
这篇文章也很好读。
-
"那么技术上不正确吗?有人可以澄清一下吗?" - 我可以澄清一下;你问这个问题吗? (我只是想明确你是否要求回答)。如果你问:它可能作为一个单独的问题更好地工作;这里不太可能收集新的答案作为答案
-
我已经回答了这个问题并且问了一个单独的问题,我在这里有疑问stackoverflow.com/questions/53654006/谢谢@MarcGravell。你现在可以删除你的删除投票吗?
-
"你现在可以删除你的删除投票吗?" - 那不是我的;多亏了?,我的任何投票都会立即生效。但是,我不认为这回答了问题的关键点,即关于死锁行为。