在Async / Await方法中使用ThreadPool和Task.Wait

时间:2016-08-11 13:12:59

标签: c# .net async-await task-parallel-library

我刚刚遇到这段代码。我立即开始畏缩和自言自语(不是好事)。事情是我不明白为什么,也不能合理地阐明它。它对我来说真的很糟糕 - 也许我错了。

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.QueueUserWorkItemTask.Run创建线程。此外,它看起来有可能在重负载时死锁或造成严重的资源问题。我对么?

_billing.ProcessAsync()方法是 awaitable (async),所以我希望一个简单的“await”关键字是正确的,而不是所有这些其他的包袱。

2 个答案:

答案 0 :(得分:6)

我认为斯科特认为ThreadPool.QueueUserWorkItem 应该是 HostingEnvironment.QueueBackgroundWorkItem是正确的。但是,对Task.RunWait的调用完全没有意义 - 当代码已经在线程池上时,它们将工作推送到线程池并阻塞线程池线程。 / 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);

也许还不错。