【问题标题】:Interacting with the UI thread from an Async callback method?从异步回调方法与 UI 线程交互?
【发布时间】:2010-12-16 17:44:21
【问题描述】:

我有一个在System.Net.Sockets.NetworkStream.BeginRead 完成时异步调用的方法。

 skDelegate = New AsyncCallback(AddressOf skDataReceived)
 skStream.BeginRead(skBuffer, 0, 100000, skDelegate, New Object)

在那个回调方法中,我需要与 UI 线程交互。

Sub skDataReceived(ByVal result As IAsyncResult)
    CType(My.Application.OpenForms.Item("frmMain"), frmMain).refreshStats(d1, d2)
End Sub

这会在方法完成后导致异常。 (执行 End Sub 时)

撤消操作遇到了 与什么不同的上下文 在对应的Set中应用 手术。可能的原因是 在线程上设置了一个上下文,并且 未还原(撤消)。

那么如何通过回调方法与 UI 线程交互呢?我做错了什么?

【问题讨论】:

    标签: c# vb.net multithreading asynchronous ui-thread


    【解决方案1】:

    您必须在 frmMain 对象上使用 Invoke 或 BeginInvoke 来将消息(委托)排入队列以在 UI 线程上执行。

    这是我在 C# 中的做法。

    frmMain.Invoke(() => frmMain.refreshStats(d1, d2));

    也可以查看list of Invoke types and their uses

    【讨论】:

    • 如果我直接在 frmMain 上调用,我会收到一条错误消息,指出只能在创建窗口句柄后在控件上调用调用。所以我尝试使用 OpenForms 集合中的表单,但是我得到了同样的旧错误,“撤消操作......”我做错了什么?
    • 换句话说,您的“解决方案”NOT 解决了我的基本问题,即 InvalidContextException。无论如何,如果有人再次遇到这个问题,我已经找到了答案。请参阅下面的答案。
    • 所以基本上我认为正在发生的是这段代码在 Windows 为窗体窗口创建 Win32 句柄之前执行。我很好奇,这段代码运行前会显示窗口吗?
    【解决方案2】:

    特拉维斯是正确的。 Windows 窗体应用程序是单线程的,您无法从任何其他线程访问 UI。您需要使用 BeginInvoke 编组对 UI 线程的调用。

    见:http://msdn.microsoft.com/en-us/library/0b1bf3y3.aspx

    【讨论】:

      【解决方案3】:

      我找到了解决方案(实际上是解决方法!)每当我在 UI 线程上交互或什至从表单中读取属性时遇到的反复出现的 InvalidContextException 错误。

      在通过异步回调方法与 UI 线程交互之前和之后,我必须备份和恢复执行上下文。然后异常会像它出现时一样神秘地消失,您可以读取/写入属性、调用方法以及使用 UI 线程基本上做任何您喜欢的事情,与您的异步回调同步,而无需使用委托或调用!

      这个异常实际上是 .NET 框架本身的一个低级错误。请参阅Microsoft Connect bug report,但请注意,它们没有列出功能性变通方法。

      解决方法:(生产代码)

      Sub skDataReceived(ByVal result As IAsyncResult)
      
          // backup the context here
          Dim syncContext As SynchronizationContext = AsyncOperationManager.SynchronizationContext
      
          // interact with the UI thread
          CType(My.Application.OpenForms.Item("frmMain"), frmMain).refreshStats(d1, d2)
      
          // restore context.
          AsyncOperationManager.SynchronizationContext = syncContext
      End Sub
      

      【讨论】:

        【解决方案4】:

        您需要让 UI 线程调用 frmMain.refreshStats 方法。当然,有很多方法可以使用 Control.InvokeRequired 属性和 Control.Invoke (MSDN Documentation)。

        您可以让“EndAsync”方法使方法调用 UI 线程安全,或者让 refreshStats 方法检查线程安全(使用 Control.InvokeRequired)。

        EndAsync UI 线程安全应该是这样的:

        Public Delegate Sub Method(Of T1, T2)(ByVal arg1 As T1, ByVal arg2 As T2)
        
        Sub skDataReceived(ByVal result As IAsyncResult)
            Dim frmMain As Form = CType(My.Application.OpenForms.Item("frmMain"), frmMain)
            Dim d As Method(Of Object, Object)
        'create a generic delegate pointing to the refreshStats method
            d = New Method(Of Object, Object)(AddressOf frmMain.refreshStats)
        'invoke the delegate under the UI thread
            frmMain.Invoke(d, New Object() {d1, d2})
        End Sub
        

        或者你可以让 refreshStats 方法检查它是否需要在 UI 线程下调用自己:

        Public Delegate Sub Method(Of T1, T2)(ByVal arg1 As T1, ByVal arg2 As T2)
        
        Sub refreshStats(ByVal d1 As Object, ByVal d2 As Object)
        'check to see if current thread is the UI thread
            If (Me.InvokeRequired = True) Then
                Dim d As Method(Of Object, Object)
        'create a delegate pointing to itself
                d = New Method(Of Object, Object)(AddressOf Me.refreshStats)
        'then invoke itself under the UI thread
                Me.Invoke(d, New Object() {d1, d2})
            Else
                'actual code that requires UI thread safety goes here
            End If
        End Sub
        

        【讨论】:

        • 尝试了所有这些伙伴,他们只是没有解决实际的异常!请参阅我的解决方法。
        猜你喜欢
        • 2019-04-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-06-02
        • 1970-01-01
        • 2015-08-05
        相关资源
        最近更新 更多