我刚刚遇到这段代码。我立即开始畏缩和自言自语(不是好事)。事情是我不明白为什么,也不能合理地阐明它。它对我来说真的很糟糕 - 也许我错了。
public async Task<IHttpActionResult> ProcessAsync()
{
var userName = Username.LogonName(User.Identity.Name);
var user = await _user.GetUserAsync(userName);
ThreadPool.QueueUserWorkItem((arg) =>
{
Task.Run(() => _billing.ProcessAsync(user)).Wait();
});
return Ok();
}
这段代码让我觉得它不必要地用ThreadPool.QueueUserWorkItem
和Task.Run
创建线程。此外,它看起来有可能在重负载时死锁或造成严重的资源问题。我对么?
_billing.ProcessAsync()方法是 awaitable (async),所以我希望一个简单的“await”关键字是正确的,而不是所有这些其他的包袱。
答案 0 :(得分:6)
我认为斯科特认为ThreadPool.QueueUserWorkItem
应该是 HostingEnvironment.QueueBackgroundWorkItem
是正确的。但是,对Task.Run
和Wait
的调用完全没有意义 - 当代码已经在线程池上时,它们将工作推送到线程池并阻塞线程池线程。 / p>
_billing.ProcessAsync()方法是等待的(异步),所以我希望一个简单的“await”关键字是正确的,而不是所有这些其他的包袱。
我非常同意。
然而,这会改变操作的行为。现在它将等到Billing.ProcessAsync
完成,而在它提前返回之前。请注意,早期返回ASP.NET几乎总是一个错误 - 我会说任何“计费”处理都会更加错误。因此,使用await
替换此混乱将使应用程序更正确,但这会导致ProcessAsync
操作需要更长时间才能返回到客户端。
答案 1 :(得分:3)
这很奇怪,但是根据作者想要实现的目标,我似乎可以从异步方法中对线程池中的工作项进行排队。
这不是启动一个线程,它只是在有一个免费的线程时在ThreadPool的线程中排队一个动作。因此,异步方法(ProcessAsync
)可以继续,不需要关心结果。
奇怪的是lambda中的代码要在ThreadPool中排队。不仅Task.Run()
(这是超级的并且只是导致不必要的开销),而且在没有等待的情况下调用async
方法来完成它是不好的方法应该是由ThreadPool运行,因为它在await
某事物时将控制流返回给调用者。
所以ThreadPool最终认为这个方法已经完成(并且该队列中的下一个动作可以使用该线程),而实际上该方法希望稍后恢复。
这可能导致非常未定义的行为。此代码可能一直在工作(在某些情况下),但我不依赖并将其用作生产代码。
(在Task.Run()
内调用一个未经等待的异步方法也是如此,因为Task
“认为”它已完成,而方法实际上希望稍后恢复。“
作为解决方案,我建议简单await
使用async
方法:
await _billing.ProcessAsync(user);
但当然,如果不了解代码片段的上下文,我无法保证任何事情。 注意这会改变行为:直到现在代码都没有等待_billing.ProcessAsync()
finsih,它现在会这样做。所以可能会遗漏await
而只是开火并忘记
_billing.ProcessAsync(user);
也许还不错。