Async/Await 仍然阻塞 UI?

Posted

技术标签:

【中文标题】Async/Await 仍然阻塞 UI?【英文标题】:Async/Await is still blocking the UI? 【发布时间】:2019-11-22 03:03:54 【问题描述】:

对 Async/Await 不是很熟悉,但是有这个 sn-p 的代码,并且在调用它时仍然会阻塞 UI?我只是得到“等待”光标,无法移动窗口等。

    Public Async Function IsPortReachable(ByVal strHost As String, Optional ByVal intPort As Integer = 80, Optional ByVal intTimeoutMs As Integer = 5000) As Task(Of Boolean)

        ' Throw an exception if no host was passed
        If String.IsNullOrEmpty(strHost) Then Throw New ArgumentNullException(NameOf(strHost))

        Return Await Task.Run(Function()

                                  Dim clientDone As ManualResetEvent = New ManualResetEvent(False)
                                  Dim bReachable As Boolean = False
                                  Dim hostEntry As DnsEndPoint = New DnsEndPoint(strHost, intPort)

                                  Using socket = New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)

                                      Dim socketEventArg = New SocketAsyncEventArgs With .RemoteEndPoint = hostEntry

                                      AddHandler socketEventArg.Completed, Sub(s, e)
                                                                               bReachable = e.SocketError = SocketError.Success
                                                                               clientDone.Set()
                                                                           End Sub

                                      clientDone.Reset()
                                      socket.ConnectAsync(socketEventArg)
                                      clientDone.WaitOne(intTimeoutMs)

                                      Return bReachable

                                  End Using

                              End Function).ConfigureAwait(False)
    End Function

我从这样的主窗体中调用它,这也发生在 Form_Load 上,因此在用户能够“收回控制”程序之前存在不必要的延迟:

Dim bTask As Task(Of Boolean) = IsPortReachable(dicValidated.Item("DNS2Up"), Convert.ToInt32(dicValidated.Item("DNS2UpPort")), 1000)

我的印象是 Async/Await 函数在不同的线程上运行,从而使调用线程(UI 线程)没有阻塞?

【问题讨论】:

不要使用.ConfigureAwait UI 代码。 如果你将你的内部任务函数(使用Dim clientDone...)移动到它自己的***函数,你的代码会更容易阅读。 您使用非异步套接字 API 有什么原因吗? 异步不创建线程。恰恰相反:它允许多个任务共享一个线程。如果它总是创建线程,那么我们就不需要发明 Async。我们只会使用线程。 【参考方案1】:

调用异步方法不会启动或使用不同的线程,也不会使该方法的执行异步执行。只有在该方法中第一次出现 await 操作时,才会将执行返回给调用者,并连接一个异步延续,以便稍后异步运行该方法的其余部分(有一个例外,但现在保持简单)。

但即使是这种延续也可能在调用线程上运行,尤其是在涉及同步上下文的情况下,例如在使用 WinForms 或 WPF 的情况下。它可能会导致死锁,但您已经通过添加 ConfigureAwait(false) 解决了它。

如果我理解正确(并且代码建议相同),您的代码中没有死锁,您只会在 UI 中体验“短暂”的无响应。我怀疑你稍后在Form_Load 函数中调用bTask.Result,这会阻塞UI 线程直到IsPortReachable 完成其工作。将此阻止代码更改为 await bTask 应该可以解决您的问题。

旁注:我也会避免使用 ManualResetEvent 并使用 await 调用 socket.ConnectAsync()。这种情况你也可以摆脱Task.Run(...)

【讨论】:

以上是关于Async/Await 仍然阻塞 UI?的主要内容,如果未能解决你的问题,请参考以下文章

[译]async/await中使用阻塞式代码导致死锁

Kotlin Coroutine,Android Async Task 和 Async await 的区别

async/await 会阻塞事件循环吗? [复制]

NetTcpBinding 和 async/await WCF 阻塞

使用 async/await 仍然返回 undefined

Async/Await替代Promise的理由