为什么MainForm的值副本创建时调用方法或调用横纱?(Why is a value copy o

2019-07-17 23:02发布

更新:我认为这事做的窗口句柄的MainForm的延迟实例-但一直没能制定出相当如何将导致这里看到的行为。

通过第三方COM接口的应用程序请求数据提供一个回调来处理结果。 在回调中,UI需要更新 - 但预期更新不起作用。 这是因为如果MainForm中的值复制已经建立,当MainForm.DataReady被调用或直接调用跨线程,但UI更新工作从事件处理程序执行时预期。 你能解释一下为什么吗?

(注: AppDomain.CurrentDomain.Id总是1中的MainForm或ClassB的是否检查。)

初始代码 -从ClassB的实例调用到DataReady而不的MainForm InvokeRequred /代表/调用逻辑。 应用程序UI变化按预期工作,MainForm中SomeListControl.EmptyListMsg = "Not Available"改变不会“大棒”(仿佛适用于MainForm的一个单独的副本)



Module AppGlobals
  Public WithEvents A As ClassA
End Module

Partial Friend Class MyApplication
  Private Sub MyApplication_Startup(ByVal sender As Object,
                                          ByVal e As StartupEventArgs) Handles Me.Startup
    A = New ClassA()

  End Sub
End Class

Class MainForm

  private sub getData
    ToggleWait(True)
    SomeListControl.Clear()
    A.getData() 'Sets up the com object & callback
  end sub

  Public Sub DataReady()
    ToggleWait(False)
    ' Do something with the data
  End Sub

  Private Sub ToggleWait(toggle as Boolean)
    Application.UseWaitCursor = False
    if toggle then
      SomeListControl.EmptyListMsg = "Not Available"
    else
      SomeListControl.EmptyListMsg = "Please Wait"
    end if
  End Sub

End Class

Class ClassA

  public sub getData()
     Dim ComObj as New ComObject
     Call ComObj.setClient(New ClassB)
  End Sub

End Class

Class ClassB
  Implements IComObjectClient

  sub getdata_callback(results() as Object) handles IComObjectClient.getdata_callback
    ' Get the results
    MainForm.DataReady() 
  end sub

End Class

新增InvokeRequred逻辑DataReady,还是直接从ClassB的调用 。 InvokeRequired是不正确的,应用程序UI变化按预期工作,MainForm中SomeListControl.EmptyListMsg = "Not Available"改变不会“大棒”(仿佛适用于MainForm的一个单独的副本)


  Class MainForm
    Public Delegate Sub DataReadyDelegate(ByVal toggle As Boolean)
    ...
    Public Sub DataReady()
        If InvokeRequired Then
            Invoke(New DataReadyDelegate()
        Else
          ToggleWait(False)
          ' Do something with the data
        End If
    End Sub
    ...
  End Class

调用MainForm.DataReady直接从ClassB的了异常:“调用或BeginInvoke可直到窗口句柄已创建不能在一个控件调用。” 直到我强迫的窗口句柄创建。 然后,它的相同的行为之前,即InvokeRequired是不正确的,应用程序UI变化按预期工作,MainForm中SomeListControl.EmptyListMsg = "Not Available"改变不会“大棒”(仿佛适用于MainForm的一个单独的副本)


Class ClassB
  Implements IComObjectClient
  Public Delegate Sub DataReadDelegate()

  sub getdata_callback(results() as Object) handles IComObjectClient.getdata_callback
    ' Get the results 
    If Not MainForm.IsHandleCreated Then
      ' This call forces creation of the control's handle
      Dim handle As IntPtr = MainForm.Handle
    End If
    MainForm.Invoke(New DataReadyDelegate(AddressOf MainForm.DataReady))
  end sub

End Class

从事件处理程序定义的自定义执行 “得到的数据”在ClassA和ClassB的事件。 ClassA的监听ClassB.got_data_event,提高ClassA.got_data_event,MainForm的监听ClassA.got_data_event并通过调用DataReady处理它()。 此工程 - InvokeRequired为真,则调用excuted,应用程序UI和预期的MainForm UI变化的工作。


  Class MainForm
    Public Delegate Sub DataReadyDelegate()
    ...
    Public Sub DataReady()
        If InvokeRequired Then
            Invoke(New DataReadyDelegate()
        Else
          ToggleWait(False)
          ' Do something with the data
        End If
    End Sub

    Public Sub _GotData_HandleEvent(ByVal resultMessage As String)
        DataReady()
    End Sub

    Private Sub MainForm_Load(sender As Object, e As EventArgs) Handles Me.Load
        ...
        ToggleWait(False)
        AddHandler A.GotData, AddressOf _GotData_HandleEvent
        ...
    End Sub
    ...
  End Class

Answer 1:

对比:

  A.getData() 

有:

  If Not MainForm.IsHandleCreated Then

您正在使用的第一条语句正确的面向对象的编程语法。 A是一个对象 。 该Form.IsHandleCreated属性是一个实例属性,它需要在左侧的对象名称。 但是,您使用的类型名称 。 MainForm中不是一个对象,它是在你的代码中的类型。

这是可能是一个非常讨厌的VB.NET功能。 它的存在是为了帮助VB6程序员转移到VB.NET编码,VB6强烈使用窗体的类型名称鼓舞。 从语法继承VB1 VB4实施任何类似对象之前。

现在,这肯定是一个便利。 您可以通过简单地使用类型名称引用窗体对象在另一个类。 请注意,您怎么没有用的目的是方便。 您可以通过使其成为一个全局变量,其存储在模块解决了这个问题。 这不赢任何价格要么,但没让你引用一个在任何类。

问题是,当你开始使用假的表单对象在另一个线程这种便利变成致命的 。 什么,你没指望的是,这个对象有<ThreadLocal>范围。 换句话说,当你在一个工作线程使用它,那么你得到的类的MainForm的对象。 这种形式的对象是不可见的,你从来没有所谓的Show()方法。 不,这会工作,线程不泵消息循环,这样的形式将不会画画本身正常。 你观察到的另一个副作用是,其InvokeRequired属性不行为。 返回FALSE。 这也是正确的,在形式的工作线程创建的,因此你没有真正使用的BeginInvoke()。 不,这会工作,要么,但它仍然是一个错误的对象,而不是用户正在看一个。

所以一个Q&d解决方法是做同样的事情与表单对象为您提供的目的一样,将其存储在一个全局变量:

Module AppGlobals
  Public WithEvents A As ClassA
  Public MainWindow As MainForm
End Module

而从类的构造函数初始化:

Class MainForm
    Sub New()
        InitializeComponent()
        MainWindow = Me
    End Sub
'' etc..
End Class

现在,你可以参考主窗口中的类。 ,你会得到MainForm类的实际情况是,用户在看一个参考。 并从MainWindow.InvokeRequired正确的返回值。

这将解决你的问题,但它仍然是难看,而且容易出错。 正确的做法是这样的:

Public Class MainForm
    Private Shared MainWindow As MainForm

    Public Shared ReadOnly Property Instance() As MainForm
        Get
            '' Return a reference to the one-and-only instance of MainForm
            If MainWindow Is Nothing Then
                '' It doesn't exist yet so create an instance 
                '' Creating one on a worker thread will never work, so complain
                If System.Threading.Thread.CurrentThread.GetApartmentState() <> Threading.ApartmentState.STA Then
                    Throw New InvalidOperationException("Cannot create a window on a worker thread")
                End If
                New MainForm()
            End If
            Return MainWindow
        End Get
    End Property

    Protected Overrides Sub OnFormClosed(ByVal e As System.Windows.Forms.FormClosedEventArgs)
        '' Ensure that the one-and-only instance is now Nothing since it closed
        MyBase.OnFormClosed(e)
        MainWindow = Nothing
    End Sub

    Sub New()
        '' Creating more than once instance of this form can't work, so complain
        If MainWindow IsNot Nothing Then Throw New InvalidOperationException("Cannot create more than one instance of the main window")
        InitializeComponent()
        '' We need to keep track of this instance since the Instance property returns it
        MainWindow = Me
    End Sub

    '' etc...
End Class

现在你可以在你的类任何地方使用MainForm.Instance,像MainForm.Instance.InvokeRequired。 你会当你得到它错异常提醒。



文章来源: Why is a value copy of MainForm created when method is called or invoked cross thread?