异步方法上的Task.Wait()在本地工作,但不能通过另一种方法

时间:2019-03-14 11:42:11

标签: c# asynchronous async-await

我最近解决了尝试执行异步方法时遇到的问题。对我来说,这提出了比解决还多的问题。简而言之,我很高兴它能起作用,但是我不知道为什么会起作用。

我正在尝试运行Microsoft Graph Client库的以下方法:

_graphServiceClient.Users[userPrincipalName].Request().GetAsync()

以下方法无效,因为它在使用await后挂起:

async Task<User> GetUser(string userPrincipalName)
{
    User user = await _graphServiceClient.Users[userPrincipalName].Request().GetAsync();
    return user;
}

以下方法也行不通,因为它在执行runTask.Wait()后挂起:

User GetUser(string userPrincipalName)
{   
    return (User) GetResult(_graphServiceClient.Users[userPrincipalName].Request().GetAsync());
}

object GetResult<TResult>(Task<TResult> task)
{
    using (task)
    using (var runTask = Task.Run(async () => await task))
    {
        try
        {
            runTask.Wait();
            return runTask.Result;
        }
        catch (AggregateException e)
        {
            throw e.InnerException ?? e;
        }
    }
}

这是奇怪的地方,因为下面的代码确实起作用了:

User GetUser(string userPrincipalName)
{   
    using (var task = Task.Run(async () => await _graphServiceClient.Users[userPrincipalName].Request().GetAsync()))
    {
        try
        {
            task.Wait();
            return task.Result;
        }
        catch (AggregateException e)
        {
            throw e.InnerException ?? e;
        }
    }
}

虽然我意识到后两种方法不被视为最佳实践,但我完全困惑第三种方法为何有效而第二种无效的原因。在我看来,这些几乎相同。

那为什么第三个方法有效,而第二个方法却无效?

1 个答案:

答案 0 :(得分:4)

首先,您需要了解how the deadlock happensawait(默认情况下)将捕获“上下文”,并将resume executing the rest of the async method in that context。在ASP.NET Classic情况下,此“上下文”是请求上下文,仅允许一次运行一个线程。在线程池线程上运行时,“上下文”是线程池上下文,它仅将async方法的其余部分排队到线程池中,该线程可以在任何线程池线程中运行。

Blocking on asynchronous code is an antipattern。在具有单线程上下文的情况下(例如ASP.NET Classic),您可能会陷入死锁。在第一个死锁示例中,在ASP.NET Classic上下文中调用了GetUser,因此其await将捕获该上下文。然后,调用代码(也在ASP.NET Classic上下文中运行)将阻止该任务。这样会在该上下文中阻塞线程,从而阻止GetUser完成,因此最终导致死锁。

但是,即使您没有陷入僵局,您仍然仍然会首先放弃异步代码的所有好处。考虑“工作”示例,该示例属于另一个antipattern (Task.Run on ASP.NET)。在这种情况下,Task.Run导致GetAsync在线程池上下文中运行,因此在ASP.NET Classic上下文中阻止线程不会死锁。但是,这也是一种反模式。

正确的解决方案是async all the way。具体来说,您的控制器操作应为async,并在调用异步代码时使用await。只需假装Wait()Result不存在,您的代码就会更加快乐。