C# 使用线程池和Task.Wait在异步/等待方法中

C# 使用线程池和Task.Wait在异步/等待方法中,c#,.net,async-await,task-parallel-library,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);

我刚刚遇到了这个代码。我立刻开始畏缩,自言自语(不是好事)。问题是我真的不明白为什么,也不能合理地表达出来。我只是觉得很糟糕——也许我错了

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();
}
公共异步任务ProcessAsync()
{
var userName=userName.LogonName(User.Identity.Name);
var user=await\u user.GetUserAsync(用户名);
ThreadPool.QueueUserWorkItem((arg)=>
{
Task.Run(()=>_billing.ProcessAsync(用户)).Wait();
});    
返回Ok();
}
在我看来,这段代码似乎不必要地使用
ThreadPool.QueueUserWorkItem
Task.Run
创建线程。另外,它看起来有可能在重载时死锁或造成严重的资源问题。我说得对吗


_billing.ProcessAsync()方法是可等待的(async),因此我认为一个简单的“await”关键字将是正确的选择,而不是所有其他的包袱。

这很奇怪,但根据作者试图实现的目标,从异步方法内部将工作项排入线程池似乎是可以的

这并不是启动一个线程,它只是在线程池的线程中有空闲线程时将要执行的操作排队。因此,异步方法(
ProcessAsync
)可以继续,不需要关心结果

奇怪的是lambda中的代码要排队到线程池中。不仅是
Task.Run()
(这是超级函数,只会导致不必要的开销),而且在线程池应该运行的方法中调用
async
方法是不好的,因为它在
等待
等待某些内容时会将控制流返回给调用方

因此,线程池最终认为该方法已完成(队列中的下一个操作没有线程),而实际上该方法希望稍后恢复

这可能导致非常未定义的行为。这段代码可能一直在工作(在某些情况下),但我不会依赖它,而将其用作生产代码

(在
Task.Run()
中调用一个未等待的异步方法也是如此,因为
Task
“认为”它已经完成,而该方法实际上希望稍后恢复)


作为解决方案,我建议简单地
wait
也使用
async
方法:

await _billing.ProcessAsync(user);
当然,如果不知道代码片段的上下文,我不能保证任何事情注意这将改变行为:虽然到目前为止,代码没有等待
\u billing.ProcessAsync()
到finsih,但现在可以了。所以,也许不必等待,只需开火然后忘记

_billing.ProcessAsync(user);

也许也足够好。

我相信Scott的猜测是正确的,
ThreadPool.QueueUserWorkItem
应该是
HostingEnvironment.QueueBackgroundWorkItem
。然而,调用
Task.Run
Wait
是完全没有意义的-它们将工作推送到线程池并在线程池上阻塞线程池线程,而代码已经在线程池上

_billing.ProcessAsync()方法是可等待的(async),因此我认为一个简单的“wait”关键字是正确的选择,而不是所有其他的包袱

我非常同意


但是,这将改变操作的行为。它现在将等待Billing.ProcessAsync完成,而在此之前它将提前返回。请注意,在ASP.NET上提前返回几乎总是一个错误——我想说,任何“计费”处理都更肯定是一个错误。因此,用
await
替换这个混乱将使应用程序更加正确,但会导致
ProcessAsync
操作需要更长的时间才能返回到客户端。

是的,这非常糟糕。此外,这看起来像是在网页中运行的代码,就像他们执行的
ThreadPool.QueueUserWorkItem
一样,实际上是要执行的,如果您在IIS上有后台线程,这就是您应该执行的操作。非常好的响应。我认为简单的“等待”将是推荐的治疗方法。你同意吗?@bigdady是的,忘了结论。。。据我所知,
wait
是正确的。如果QueueUserWorkItem中包装的任务是一个长期运行的操作,它会改变什么吗?@bigdady在不知道整个项目的情况下仍然很难说。由于答案中所述的原因,代码被破坏。使用
QueueUserWorkItem
Task.Run()
,或者简单地调用
async
方法(带或不带
await
)会导致该方法在线程池线程中执行。如果它以前是通过
Task.Factory.StartNew(...LongRunning)
调用的,那么它将在一个专用线程中执行,而现在它阻止了一个线程池线程。谢谢,但我有点困惑。正在创建的任务正在使用“等待”,但您说它将提前返回。在这种情况下,“等待”在该任务上实际做了什么?它与“Result”类似吗?@bigdady:
Wait
只是在任务完成之前阻塞线程池线程。在这种情况下,它除了使用线程池线程之外没有任何用处-您可以完全摆脱
等待
(和
任务.Run
),代码的行为将完全相同(除了使用更少的资源)
Wait
Result
非常相似,它们都会阻塞调用线程,直到任务完成。该操作提前返回,因为它没有等待QUWI排队的工作。如果QueueUserWorkItem中包装的任务是一个长期运行的操作,它会改变什么吗?@Bigdady:不会。无论需要多长时间,QUWI在ASP.NET上本质上是不安全的。(其中“u”