通过用户控件.net 1.1 到 4.6 上的按钮,父子之间的 MDI 跨线程异常
MDI cross-thread exception between parent and child via button on usercontrol .net 1.1 to 4.6
自从我从事 Winform 应用程序工作以来,这是前所未有的。我的专长是与 Asp.net/Websites 合作。最近,我收到了一份从 .Net 1.1 升级到 .Net 4.6 的申请。该应用程序是一个 MDI 应用程序,其中存在跨线程操作问题。具体来说,当单击用户控件中的按钮事件时,目的是显示主 MDI 子窗体 (cfrmOverview),但是会发生错误,因为无法访问名为 picDisplay 的 Picturebox 控件。即使添加了代码,我仍然收到错误。我选择不使用 CheckForIllegalCrossThreadCalls 因为它会影响程序的其他部分,MSDN 也建议不要这样做。需要洞察力。
Public Delegate Sub Mydelegate(ByVal AControl As PictureBox)
Public Shared Sub CreateEnableControl(ByVal AControl As PictureBox)
AControl.Visible = True
AControl.Enabled = True
End Sub
Public Shared Sub NavigateTo(ByVal sender As System.Windows.Forms.UserControl, ByVal aNavTarget As String, Optional ByVal param As Object = Nothing)
Dim aType As Type
Dim Types() As Type
Dim aobject As Object
Try
If IsNothing(System.Reflection.Assembly.GetEntryAssembly) Then
aobject = sender.ParentForm
Types = System.Reflection.Assembly.GetAssembly(aobject.GetType).GetTypes
Else
Types = System.Reflection.Assembly.GetEntryAssembly.GetTypes
End If
Dim aForm As Windows.Forms.Form
For Each aType In Types
If aType.BaseType Is GetType(MdiChild) Then
If aType.Name = aNavTarget Then
Dim aMdiParent As Windows.Forms.Form
If TypeOf (sender.ParentForm) Is MdiParent Then
aMdiParent = sender.ParentForm
Else
aMdiParent = sender.ParentForm.ParentForm
End If
For Each aForm In aMdiParent.MdiChildren
If aType.FullName Is aForm.GetType.FullName Then
aForm.Tag = param
'Added Code below to try to prevent Cross-Thread exception on PicDisplay found in the Main cfrmOverview Form
'that has designed time user control embedded.
'New Code Start----------------------------------------------------------------------
For Each aControl As Windows.Forms.Control In aForm.Controls.Find("picDisplay", True)
If aControl.InvokeRequired Then
Dim myArray(0) As Object
myArray(0) = New PictureBox
aControl.BeginInvoke(New Mydelegate(AddressOf CreateEnableControl), myArray)
End If
Next
'New Code End------------------------------------------------------------------------
aForm.Show() 'Cross-thread exception for picDisplay is here.
GoTo Success
End If
您 should/must1 在创建控件的线程上调用访问控件的代码。
不要混淆 Control.Invoke 和 delegate.BeginInvoke。参见 What's the difference between Invoke() and BeginInvoke()
引发错误的行可以更改为
aForm.Invoke(Sub() aForm.Show())
但是,您可能 总是 想要调用,例如,当您已经在正确的线程上执行代码时,Invoke 调用是浪费的。这就是控件具有 InvokeRequired
属性 的原因。下面是用于显示表单的 invoke-if-invokerequired 模式的具体实现。
Private Sub showForm(ByVal f As Form)
If f.InvokeRequired Then
f.Invoke(New Action(Of Form)(AddressOf showForm), f)
Else
f.Show()
End If
End Sub
' usage:
showForm(aForm)
如果您经常进行 UI 编程,您可能会发现您正在大量编写这些方法。所以你可以自动化模式
您可以将此扩展方法放在模块中。它允许你传递一个委托来做任何你想做的事情,如果需要,它将在控件的线程上被调用
<Extension()> _
Public Sub InvokeIfRequired(ByVal control As Control, action As MethodInvoker)
If control.InvokeRequired Then
control.Invoke(action)
Else
action()
End If
End Sub
' usage:
aForm.InvokeIfRequired(Sub() aForm.Show())
1 在某些情况下,从错误的线程访问控件不会引发异常,但会导致间歇性异常。根据我的经验,这是不确定的。例如,在错误的线程上检索 TextBox.Text
通常没问题,但设置 TextBox.Text
通常会引发异常。出于这个原因,无论何时在它们自己的事件处理程序之外对控件执行任何操作,或者至少在同一表单上的控件的事件处理程序之外,都使用 invoke-if-invokerequired 模式是一种很好的做法。
自从我从事 Winform 应用程序工作以来,这是前所未有的。我的专长是与 Asp.net/Websites 合作。最近,我收到了一份从 .Net 1.1 升级到 .Net 4.6 的申请。该应用程序是一个 MDI 应用程序,其中存在跨线程操作问题。具体来说,当单击用户控件中的按钮事件时,目的是显示主 MDI 子窗体 (cfrmOverview),但是会发生错误,因为无法访问名为 picDisplay 的 Picturebox 控件。即使添加了代码,我仍然收到错误。我选择不使用 CheckForIllegalCrossThreadCalls 因为它会影响程序的其他部分,MSDN 也建议不要这样做。需要洞察力。
Public Delegate Sub Mydelegate(ByVal AControl As PictureBox)
Public Shared Sub CreateEnableControl(ByVal AControl As PictureBox)
AControl.Visible = True
AControl.Enabled = True
End Sub
Public Shared Sub NavigateTo(ByVal sender As System.Windows.Forms.UserControl, ByVal aNavTarget As String, Optional ByVal param As Object = Nothing)
Dim aType As Type
Dim Types() As Type
Dim aobject As Object
Try
If IsNothing(System.Reflection.Assembly.GetEntryAssembly) Then
aobject = sender.ParentForm
Types = System.Reflection.Assembly.GetAssembly(aobject.GetType).GetTypes
Else
Types = System.Reflection.Assembly.GetEntryAssembly.GetTypes
End If
Dim aForm As Windows.Forms.Form
For Each aType In Types
If aType.BaseType Is GetType(MdiChild) Then
If aType.Name = aNavTarget Then
Dim aMdiParent As Windows.Forms.Form
If TypeOf (sender.ParentForm) Is MdiParent Then
aMdiParent = sender.ParentForm
Else
aMdiParent = sender.ParentForm.ParentForm
End If
For Each aForm In aMdiParent.MdiChildren
If aType.FullName Is aForm.GetType.FullName Then
aForm.Tag = param
'Added Code below to try to prevent Cross-Thread exception on PicDisplay found in the Main cfrmOverview Form
'that has designed time user control embedded.
'New Code Start----------------------------------------------------------------------
For Each aControl As Windows.Forms.Control In aForm.Controls.Find("picDisplay", True)
If aControl.InvokeRequired Then
Dim myArray(0) As Object
myArray(0) = New PictureBox
aControl.BeginInvoke(New Mydelegate(AddressOf CreateEnableControl), myArray)
End If
Next
'New Code End------------------------------------------------------------------------
aForm.Show() 'Cross-thread exception for picDisplay is here.
GoTo Success
End If
您 should/must1 在创建控件的线程上调用访问控件的代码。
不要混淆 Control.Invoke 和 delegate.BeginInvoke。参见 What's the difference between Invoke() and BeginInvoke()
引发错误的行可以更改为
aForm.Invoke(Sub() aForm.Show())
但是,您可能 总是 想要调用,例如,当您已经在正确的线程上执行代码时,Invoke 调用是浪费的。这就是控件具有 InvokeRequired
属性 的原因。下面是用于显示表单的 invoke-if-invokerequired 模式的具体实现。
Private Sub showForm(ByVal f As Form)
If f.InvokeRequired Then
f.Invoke(New Action(Of Form)(AddressOf showForm), f)
Else
f.Show()
End If
End Sub
' usage:
showForm(aForm)
如果您经常进行 UI 编程,您可能会发现您正在大量编写这些方法。所以你可以自动化模式
您可以将此扩展方法放在模块中。它允许你传递一个委托来做任何你想做的事情,如果需要,它将在控件的线程上被调用
<Extension()> _
Public Sub InvokeIfRequired(ByVal control As Control, action As MethodInvoker)
If control.InvokeRequired Then
control.Invoke(action)
Else
action()
End If
End Sub
' usage:
aForm.InvokeIfRequired(Sub() aForm.Show())
1 在某些情况下,从错误的线程访问控件不会引发异常,但会导致间歇性异常。根据我的经验,这是不确定的。例如,在错误的线程上检索 TextBox.Text
通常没问题,但设置 TextBox.Text
通常会引发异常。出于这个原因,无论何时在它们自己的事件处理程序之外对控件执行任何操作,或者至少在同一表单上的控件的事件处理程序之外,都使用 invoke-if-invokerequired 模式是一种很好的做法。