异步等待永远不会结束,但同步会立即运行

async await never ends but sync runs instantly

下面的代码只是检查图像的 url 是否有效。当我如下运行它时,它会立即检查并returns

 Function testUrl(ByVal myImage As String) As Boolean

    myHttpClient.BaseAddress = New Uri(imageUrl)
    myHttpResponse = myHttpClient.GetAsync(myImage).result

    success= myHttpResponse.IsSuccessStatusCode

End Function

但是当我使用 async-await 运行它时,我不知道会发生什么。因为我在等待行上设置断点并按 f10,所以我没有任何进展,我不确定是否是因为异步,它运行但如果我的断点在 success=[=11 上,我在下一行看不到任何中断=]行。

网址是有效的 url,第一个代码 returns 立即为真。谁能告诉我发生了什么事?

 Async Function testUrl_async(ByVal myImage As String) As Task(Of Boolean)

    myHttpClient.BaseAddress = New Uri(imageUrl)
    myHttpResponse = Await myHttpClient.GetAsync(myImage)

    success= myHttpResponse.IsSuccessStatusCode

End Function

编辑: 当我什至尝试下面的代码时,它什么也没做。但是没有异步等待工作

 Async Function testUrl_async() As Task(Of Boolean)

         myHttpClient.BaseAddress = New Uri("http://www.logoeps.net/")
                myImage = "wp-content/uploads/2013/06/Whosebug_logo.jpg"
         Try
           myHttpResponse = Await myHttpClient.GetAsync(myImage)

         Catch ex As Exception

End Function

        End Try

您的第一个示例有效,因为访问 TaskResult 阻塞 当前线程,直到 Result 被设置。幸运的是 HttpClient.GetAsync() 使用不同的线程(或者更可能是 I/O 完成端口)来完成它的工作,否则你的应用程序会死锁。由于这个原因,阻止异步方法通常不是一个好主意。

在第二个示例中更改为 await 意味着当前线程将 不会 阻塞结果。相反,它会在设置结果时将函数的其余部分排队为 运行。此时调用 testUrl_async() 之后的代码将是 运行。如果你只是在此时退出,testUrl_async() 的排队延续将永远没有机会 运行.

听起来您 运行 喜欢我在博客上描述的 common deadlock issue。在您的调用堆栈中,您可能调用了 Task.Wait()Task<T>.Result。这可能会导致死锁。

Await 暂停它的方法时,它首先捕获一个"context"。这个 "context" 是 SynchronizationContext.Current(除非它是 null,在这种情况下它是 TaskScheduler.Current)。如果您不熟悉 SynchronizationContext,这只是意味着如果 运行 在 UI 线程上捕获 UI 上下文,如果 ASP.NET 请求上下文它在 ASP.NET 请求线程或线程池上下文中 运行(除非您使用其他上下文)。此捕获的上下文用于恢复 Async 方法。

稍后,当 awaitable 完成时(在本例中为 HTTP Get),Async 方法会尝试恢复执行。但是,如果调用代码已 阻塞 UI 线程/ASP.NET 请求线程,则该方法无法在该上下文中执行。所以上下文线程被阻塞,等待Async方法完成,Async方法被阻塞,等待上下文空闲。死锁。

避免这种僵局的最好方法就是我在 MSDN article on async best practices 中所描述的: 一路使用Async。换句话说,将对 Task.Wait()Task<T>.Result 的任何调用替换为 Await.