【问题标题】:Understanding async await instruction during a HttpClient request了解 HttpClient 请求期间的异步等待指令
【发布时间】:2019-03-01 00:09:41
【问题描述】:

假设我有一个由 2 个项目组成的解决方案。一个古老的 WinForm 经典项目。在这个旧项目中,我有一个登录窗口。单击此登录窗口的“确定”后,我启动了一个将调用 REST API 的事件。两个应用程序同时以调试模式启动。

在我的代码中,我有这个代码:

public async Task<User> Login(string username, string password)
{
    HttpResponseMessage response = await Client.GetAsync($"api/Login?login={username}&password={password}");
    if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
        return new User();
    response.EnsureSuccessStatusCode();
    var userDto = await response.Content.ReadAsAsync<UserDto>();
    var user = userDto.ToUser();
    return user;
}

在我调用Client.GetAsync 的第一行,我调用了我的API。在我的 API 中,我正确地收到了调用,并且我正确地返回了带有我的用户对象的 Ok,或者我返回了另一个代码。有用。我的 API 有效。但后来什么都没有。我的客户永远不会继续。 Client.GetSync 似乎在等待什么。我永远不会继续评估 StatusCode 的下一步。

public async Task<User> Login(string username, string password)
{
    HttpResponseMessage response = Client.GetAsync($"api/Login?login={username}&password={password}").Result;
    if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
        return new User();
    response.EnsureSuccessStatusCode();
    var userDto = await response.Content.ReadAsAsync<UserDto>();
    var user = userDto.ToUser();
    return user;
}

没有等待的相同代码我没有问题。我的代码运行到下一步。证明我的 API 不是问题。

很明显,这是与等待/异步有关的问题。我必须做错什么,但怎么办?你能帮助我吗?和调试器有关系吗?

更多信息这里是我之前的代码图片

在我点击下一步之后。请注意,我的调用堆栈为空,代码仍在运行。

这里要求的是我调用登录的代码。我刚刚在 Sub 之前添加了 Async 字,并通过 await _authService.Login(username, password) 更改了 _authService.Login(username, password).Result

我现在工作。

Private Async Sub ButLogin_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles butLogin.Click
    DataProxies.SetToken()
    Dim _authService As IAuthenticationService = New AuthenticationService()


    Dim username As String = txtLogin.Text
    Dim password As SecureString = New NetworkCredential(String.Empty, txtPwd.Text).SecurePassword

    Dim auth As Tuple(Of Boolean, User) = Await _authService.Login(username, password)
    If (auth.Item1) Then
        Dim user As User = auth.Item2
        Name = $"{user.FirstName} {user.LastName}"
        ApiInformations.ApiToken = user.SessionToken
    End If
End Sub

【问题讨论】:

  • Same code without the await I have no problem 相反,你有一个大问题。您阻塞了等待异步操作的线程。这意味着您阻止了桌面应用程序上的 UI 线程或阻止了可以在 Web 应用程序中为另一个请求提供服务的线程。块以 spinwaits 开头,这意味着您在不做任何事情的情况下固定 CPU。在服务器应用程序中,这不仅会降低吞吐量,还会增加 CPU 利用率,并可能导致应用程序池在高负载下回收
  • 如果您遇到问题,请调试您的代码,添加适当的异常处理并找出问题所在。也许另一台服务器从未响应。也许有一个没有被捕获的异常。或者结果可能无法反序列化为UserDto
  • 我点击 F10 进入下一步然后什么也没有发生。我可以永远等待,什么都不会发生。
  • HttpClient.GetAsync 有效。 await 有效。如果您的代码阻塞,您必须查看代码中发生了什么。也许还没有回应。也许您有一个桌面应用程序并在顶部阻止 UI 线程,阻止await 返回到它。您可以使用例如var task=Login(...);MessageBox.ShowDialog(..);, await task; 轻松做到这一点。删除await 或添加.Result 并不能证明什么。没有实际代码,不知道发生了什么,只能猜测
  • 通过在我的 ButtonLogin 事件处理程序中添加 Async 并在调用登录之前等待,我可以解决此问题。

标签: c# async-await


【解决方案1】:

我刚刚在 Sub 之前添加了 Async 字,并通过 await _authService.Login(username, password) 更改了 _authService.Login(username, password).Result

一般指导是"Don't block on async code"。这是asynchronous programming best practices 之一。

阻塞异步代码是不好的,因为默认情况下await works by capturing a "context",并在该上下文中继续执行async 方法。一种上下文是 UI 上下文,它会导致 async 方法在 UI 线程上恢复执行。

所以您看到的死锁是由阻塞 UI 线程引起的。代码调用async 方法,然后阻塞UI 线程,直到async 方法完成。但是,async 方法中的 await 捕获了 UI 上下文,因此它在完成之前等待 UI 线程空闲。 UI 线程正在等待 async 方法,而 async 方法正在等待 UI 线程:死锁。

原因您的修复工作是 UI 线程不再阻塞等待 async 方法,因此不再有死锁。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-01-09
    • 2016-12-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-17
    • 2013-03-27
    • 1970-01-01
    相关资源
    最近更新 更多