简体   繁体   English

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

[英]Using ThreadPool and Task.Wait inside an Async/Await Method

I just encountered this code. 我刚遇到此代码。 I immediately started cringing and talking to myself (not nice things). 我立即开始畏缩并自言自语(不好的事情)。 The thing is I don't really understand why and can't reasonably articulate it. 问题是我不太了解为什么,也无法合理地阐明它。 It just looks really bad to me - maybe I'm wrong. 这对我来说真的很糟糕-也许我错了。

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();
}

This code looks to me like it's needlessly creating threads with ThreadPool.QueueUserWorkItem and Task.Run . 在我看来,这段代码看起来像是在不必要地使用ThreadPool.QueueUserWorkItemTask.Run创建线程。 Plus, it looks like it has the potential to deadlock or create serious resource issues when under heavy load. 另外,在高负载下,它似乎有可能陷入僵局或造成严重的资源问题。 Am I correct? 我对么?

The _billing.ProcessAsync() method is awaitable (async), so I would expect that a simple "await" keyword would be the right thing to do and not all this other baggage. _billing.ProcessAsync()方法是可以等待的 (异步),因此我希望一个简单的“ await”关键字是正确的选择,而不是其他所有麻烦。

I believe Scott is correct with his guess that ThreadPool.QueueUserWorkItem should have been HostingEnvironment.QueueBackgroundWorkItem . 我相信Scott对ThreadPool.QueueUserWorkItem 应该是 HostingEnvironment.QueueBackgroundWorkItem猜测是正确的。 The call to Task.Run and Wait , however, are entirely nonsensical - they're pushing work to the thread pool and blocking a thread pool thread on it, when the code is already on the thread pool. 但是,对Task.RunWait的调用完全是荒谬的-当代码已经在线程池中时,它们会将工作推送到线程池并阻塞线程池中的线程。

The _billing.ProcessAsync() method is awaitable(async), so I would expect that a simple "await" keyword would be the right thing to do and not all this other baggage. _billing.ProcessAsync()方法是awaitable(async),所以我希望一个简单的“ await”关键字是正确的选择,而不是其他所有事情。

I strongly agree. 我非常同意。

However , this will change the behavior of the action. 但是 ,这将改变动作的行为。 It will now wait until Billing.ProcessAsync is completed, whereas before it would return early. 现在它将等到Billing.ProcessAsync完成,然后再提早返回。 Note that returning early on ASP.NET is almost always a mistake - I would say any "billing" processing would be even more certainly a mistake. 请注意,尽早返回ASP.NET几乎总是一个错误-我想说,任何“计费”处理都肯定是一个错误。 So, replacing this mess with await will make the app more correct, but it will cause the ProcessAsync action to take longer to return to the client. 因此,用await替换此混乱将使应用程序更正确,但将导致ProcessAsync操作花费更长的时间才能返回到客户端。

It's strange, but depending on what the author is trying to achieve, it seems ok to me to queue a work item in the thread pool from inside an async method. 这很奇怪,但是根据作者想要实现的目标,我似乎可以从异步方法内部将工作项放入线程池中

This is not as starting a thread, it's just queueing an action to be done in a ThreadPool's thread when there is a free one. 这不是启动线程,而是在有空闲线程时,在ThreadPool的线程中排队要执行的操作。 So the async method ( ProcessAsync ) can continue and don't need to care about the result. 因此,异步方法( ProcessAsync )可以继续执行,不需要关心结果。

The weird thing is the code inside the lambda to be enqueued in the ThreadPool. 奇怪的是,lambda内部的代码将被放入ThreadPool中。 Not only the Task.Run() (which is superflous and just causes unnecessary overhead), but to call an async method without waiting for it to finish is bad inside a method that should be run by the ThreadPool, because it returns the control flow to the caller when await ing something. 在应该由ThreadPool运行的方法中,不仅Task.Run() (它很Task.Run() ,而且会造成不必要的开销),而且在不等待其完成的情况下调用async方法也是很糟糕的,因为它会返回ThreadPool await呼叫者时向呼叫者发送消息。

So the ThreadPool eventually thinks this method is finished (and the thread free for the next action in the queue), while actually the method wants to be resumed later. 因此,ThreadPool最终认为此方法已完成(并且该线程空闲以供队列中的下一个操作使用),而实际上该方法希望稍后再恢复。

This may lead to very undefined behaviour . 这可能导致非常不确定的行为 This code may have been working (in certain circumstances), but I would not rely on it and use it as productive code. 该代码可能已经运行了(在某些情况下),但是我不会依赖它并将其用作生产性代码。

(The same goes for calling a not-awaited async method inside Task.Run() , as the Task "thinks" it's finished while the method actually wants to be resumed later). (在Task.Run()调用未等待的异步方法Task.Run() ,因为Task “认为”它已完成,而该方法实际上想稍后再恢复)。


As solution I'd propose to simply await that async method, too: 作为解决方案,我建议也只需awaitasync方法:

await _billing.ProcessAsync(user);

But of course without any knowledge about the context of the code snippet I can't guarantee anything. 但是,当然,如果对代码段的上下文一无所知,我无法保证任何事情。 Note that this would change the behaviour: while until now the code did not wait for _billing.ProcessAsync() to finsih, it would now do. 请注意 ,这将更改行为:虽然直到现在,代码都没有等待_billing.ProcessAsync()结束,但现在可以了。 So maybe leaving out await and just fire and forget 因此,也许离开了await ,只是射后不理

_billing.ProcessAsync(user);

maybe good enough, too. 也许也足够好。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM