65.9K
CodeProject 正在变化。 阅读更多。
Home

从其他线程引发事件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.17/5 (10投票s)

2007年11月3日

CPOL

2分钟阅读

viewsIcon

70042

downloadIcon

576

一个通用函数, 有助于避免在从辅助线程引发事件时出现“CrossThreadCall-Exception”

引言

大多数情况下,引发事件是为了让拥有该对象的对象有机会显示任何类型的更改。“显示任何类型的更改”总是意味着访问一个控件。但是,当从一个侧线程引发事件时,当我们试图显示任何内容时,就会出现令人讨厌的“CrossThreadCall-Exception”。简单地说,禁止从另一个线程访问控件(异常告诉我们)。跨线程控件访问需要“转移”到从主线程访问。这可以通过 Control.Invoke 机制来完成。这意味着:创建一个访问控件的方法,创建一个它的委托,并将该委托传递给一个 Control.Invoke() 调用(在文章 如何解决“跨线程操作无效” 中很好地完成了)。
但是 - 如果我的对象想要引发一个事件 - 它没有可以传递委托的控件!
为此,它只需使用应用程序的主窗体(这也是一个控件)。

现在,我们可以找到一个通用的、有效的解决方案,用于任何跨线程事件的引发。
前提条件:我们必须根据框架约定设计我们的事件,以实现事件。
这意味着,一个完整的指定事件由 3 部分组成

  1. 一个类“MyEventArgs”,继承自 EventArgs
  2. 事件声明为“EventHandler(Of MyEventArgs)
  3. 一个“OnMyEvent(e As MyEventArgs)” - Sub,它引发事件
(如果提交的 EventArgs EventArgs.Empty,那么在 III 中不需要处理任何参数)
  1. 一个“OnMyEvent()” - 无参数的 Sub,它引发事件,提交 System.EventArgs.Empty

好的,为了让这个模式通用,快速看一下 III - OnMyEvent()
它要么具有 System.Action - 委托的签名,要么具有 System.Windows.Forms.MethodInvoker - 委托的签名。

将这些部分组合在一起,创建一个“通用的有效事件调用器”

   Public Sub InvokeAction(Of T)( _
         ByVal anAction As System.Action(Of T), _
         ByVal Arg As T, _
         Optional ByVal ThrowMainFormMissingError As Boolean = True)
      If Not ThrowMainFormMissingError AndAlso _
         Application.OpenForms.Count = 0 Then Return
      With Application.OpenForms(0)
         If .InvokeRequired Then                   'if Invoking is required...
            .Invoke(anAction, Arg)  '...pass delegate and argument to Invoke()
         Else                                                 '...otherwise...
            anAction(Arg)                       '...call the delegate directly
         End If
      End With
   End Sub

作为奖励:通过可选的传递 ThrowMainFormMissingError=False,如果没有任何 OpenForm 可用(例如,应用程序正在关闭),我们可以抑制异常。
(但通常情况下,忘记它吧。)

使用 MethodInvoker - 委托的相同过程

   Public Sub InvokeMethod( _
         ByVal aMethod As System.Windows.Forms.MethodInvoker, _
         Optional ByVal ThrowMainFormMissingError As Boolean = True)
      If Not ThrowMainFormMissingError AndAlso _
         Application.OpenForms.Count = 0 Then Return
      With Application.OpenForms(0)
         If .InvokeRequired Then
            .Invoke(aMethod)
         Else
            aMethod()
         End If
      End With
   End Sub

Using the Code

根据框架的常用模式设计您的 XYEvent (仅在“OnXYEvent()” - Sub 中引发它)
为了从侧线程引发它,调用

InvokeAction(AddressOf OnXYEvent, new XYEventArgs())

而不是

OnXYEvent(new XYEventArgs())

代码示例

我简单地展示了整个类“CountDown”。它包含了我提到的所有内容

  • 事件“Tick”提交一个用户定义的 EventArgs
  • 事件“Finished”提交 EventArgs.Empty
  • 两者都从一个侧线程引发
Imports System.Threading

Public Class CountDown

   Public Class TickEventArgs : Inherits EventArgs
      Public ReadOnly Counter As Integer
      Public Sub New(ByVal Counter As Integer)
         Me.Counter = Counter
      End Sub
   End Class 'TickEventArgs

   Public Event Tick As EventHandler(Of TickEventArgs)

   Protected Overridable Sub OnTick(ByVal e As TickEventArgs)
      RaiseEvent Tick(Me, e)
   End Sub

   Public Event Finished As EventHandler

   Protected Overridable Sub OnFinished()
      RaiseEvent Finished(Me, EventArgs.Empty)
   End Sub

   'System.Threading.Timer calls back from a side-thread
   Private _AsyncTimer As New System.Threading.Timer( _
      AddressOf AsyncTimer_Callback, _
      Nothing, Timeout.Infinite, Timeout.Infinite)

   Private _Counter As Integer

   Public Sub Start(ByVal InitValue As Integer)
      _Counter = InitValue
      _AsyncTimer.Change(0, 1000)          'Execute first callback immediately
   End Sub

   Private Sub AsyncTimer_Callback(ByVal state As Object)
      InvokeAction(AddressOf OnTick, New TickEventArgs(_Counter))  'raise Tick
      'to try a thread-unsafe call, comment out the line before, 
      ' and uncomment the line after
      'OnTick(New TickEventArgs(_Counter))
      If _Counter = 0 Then
         _AsyncTimer.Change(Timeout.Infinite, Timeout.Infinite)    'stop timer
         InvokeMethod(AddressOf OnFinished)                    'raise Finished
      End If
      _Counter -= 1                                       'do a countdowns job
   End Sub

End Class

关注点

  • 正如您所看到的,对 InvokeAction() 的调用不需要指定 TypeParameter。它从传递的参数推断出来。
    TypeParameter-效果是强制执行,如果首先传递一个 Action(Of EventArgs),则第二个参数只接受 EventArgs (而不是废话)。
  • 这些调用器不仅对传输事件引发器有用,而且还可以将任何 System.Action 或 MethodInvoker 调用 传输到主线程。
  • Control.Invoke() 速度很慢。不要在侧线程引发的事件处理程序中填充 TreeView 的数百个节点。

(无抄袭)

我已经在 另一个 VB.NET - 平台 上发布了这个问题。

© . All rights reserved.