我知道如何使用BackgroundWorker(WinForms设计器中的gui对象),并手动实例化将自定义事件提升到UI的线程,但是,我在查找如何使用ThreadPool对象(最简单的形式)时遇到了一些麻烦处理将事件提升到表单以进行“安全”UI操作。
示例如下:
Public Class Form1
WithEvents t As Tools = New Tools
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
t.Unzip("file 1", "foo")
t.Unzip("file 2", "foo")
t.Unzip("file 3", "foo")
t.Unzip("file 4", "foo")
t.Unzip("file 5", "foo")
t.Unzip("file 6", "foo")
t.Unzip("file 7", "foo")
t.Unzip("file 8", "foo")
t.Unzip("file 9", "foo")
End Sub
Private Sub t_UnzipComplete(ZipInfo As Tools.ZipInfo) Handles t.UnzipComplete
TextBox1.Text = TextBox1.Text & ZipInfo.ZipFile & vbCr
End Sub
End Class
(为演示添加多行文本框和此表单的按钮)
Imports System
Imports System.Threading
Imports System.IO.Compression
Public Class Tools
#Region "Zip"
Private _zip As System.IO.Compression.ZipFile
Public Shared Event UnzipComplete(ByVal ZipInfo As ZipInfo)
Public Shared Event ZipComplete(ByVal ZipInfo As ZipInfo)
Public Class ZipInfo
Public Property ZipFile As String
Public Property Path As String
End Class
Public Sub Unzip(ByVal ZipFile As String, ByVal Destination As String)
Dim _ZipInfo As New Tools.ZipInfo
_ZipInfo.ZipFile = ZipFile
_ZipInfo.Path = Destination
ThreadPool.QueueUserWorkItem(AddressOf ThreadUnzip, _ZipInfo)
End Sub
Public Sub Zip(ByVal Folder As String, ByVal ZipFile As String)
Dim _ZipInfo As New Tools.ZipInfo
_ZipInfo.ZipFile = ZipFile
_ZipInfo.Path = Folder
ThreadPool.QueueUserWorkItem(AddressOf ThreadUnzip, _ZipInfo)
End Sub
Shared Sub ThreadUnzip(ZipInfo As Object)
RaiseEvent UnzipComplete(ZipInfo)
End Sub
Shared Sub ThreadZip(ZipInfo As Object)
RaiseEvent ZipComplete(ZipInfo)
End Sub
#End Region
End Class
此代码应该执行的操作如下:
在Form1上引发的事件应该是UI安全的,因此我可以使用传递给Textbox中的ZipCompleted / UnzipCompleted事件的信息。这应该是通用的,这意味着引发事件的函数应该是可重用的,并且不会直接调用表单。 (也就是说,我不想在Tools.vb中调用Form1.vb上的特定元素的“自定义”子或函数。这应该是通用的,可以通过将类添加到我的项目然后在下面输入任何“自定义”表单代码来重复使用引发的事件(例如,当引发Button1_Click时,即使它是线程化的,其他表单交互也不是Button1对象/类的一部分 - 它们由编码器写入用户单击时引发的事件。
答案 0 :(得分:2)
如果要确保对UI没有直接了解的对象在UI线程上引发其事件,请使用SynchronizationContext
类,例如
Public Class SomeClass
Private threadingContext As SynchronizationContext = SynchronizationContext.Current
Public Event SomethingHappened As EventHandler
Protected Overridable Sub OnSomethingHappened(e As EventArgs)
RaiseEvent SomethingHappened(Me, e)
End Sub
Private Sub RaiseSomethingHappened()
If Me.threadingContext IsNot Nothing Then
Me.threadingContext.Post(Sub(e) Me.OnSomethingHappened(DirectCast(e, EventArgs)), EventArgs.Empty)
Else
Me.OnSomethingHappened(EventArgs.Empty)
End If
End Sub
End Class
只要在UI线程上创建该类的实例,就会在UI线程上引发其SomethingHappened
事件。如果没有UI线程,则只会在当前线程上引发该事件。
这是一个更完整的示例,其中包含一个使用Lambda表达式的简单方法:
Imports System.Threading
Public Class Form1
Private WithEvents thing As New SomeClass
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Me.thing.DoSomethingAsync()
End Sub
Private Sub thing_DoSomethingCompleted(sender As Object, e As IntegerEventArgs) Handles thing.DoSomethingCompleted
MessageBox.Show(String.Format("The number is {0}.", e.Number))
End Sub
End Class
''' <summary>
''' Raises events on the UI thread after asynchronous tasks, assuming the instance was created on a UI thread.
''' </summary>
Public Class SomeClass
Private ReadOnly threadingContext As SynchronizationContext = SynchronizationContext.Current
Public Event DoSomethingCompleted As EventHandler(Of IntegerEventArgs)
''' <summary>
''' Begin an asynchronous task.
''' </summary>
Public Sub DoSomethingAsync()
Dim t As New Thread(AddressOf DoSomething)
t.Start()
End Sub
Protected Overridable Sub OnDoSomethingCompleted(e As IntegerEventArgs)
RaiseEvent DoSomethingCompleted(Me, e)
End Sub
Private Sub DoSomething()
Dim rng As New Random
Dim number = rng.Next(5000, 10000)
'Do some work.
Thread.Sleep(number)
Dim e As New IntegerEventArgs With {.Number = number}
'Raise the DoSomethingCompleted event on the UI thread.
Me.threadingContext.Post(Sub() OnDoSomethingCompleted(e), Nothing)
End Sub
End Class
Public Class IntegerEventArgs
Inherits EventArgs
Public Property Number() As Integer
End Class
答案 1 :(得分:1)
您应该从Form注册到Tools类的事件(您已经定义了这些事件),当然实际事件将在非UI线程下触发,因此它在回调期间执行的代码只会是能够通过Invoke()
你想简单地在Tools
类中引发事件,需要完成Invoke,因为你想要更新UI,Tools类应该关注它。
更改您的事件处理:
Private Sub t_UnzipComplete(ZipInfo As Tools.ZipInfo) Handles t.UnzipComplete
TextBox1.Invoke(Sub () t_UnzipComplete(ZipInfo))
End Sub
要从视图中注册事件:(这将进入Button1_Click
事件
AddHandler t.UnzipComplete, AddressOf t_UnzipComplete
确保您只注册一次
答案 2 :(得分:1)
这会解决您的问题吗?
Private Sub t_UnzipComplete(ZipInfo As Tools.ZipInfo) Handles t.UnzipComplete
If TextBox1.InvokeRequired Then
TextBox1.Invoke(Sub () t_UnzipComplete(ZipInfo))
Else
TextBox1.Text = TextBox1.Text & ZipInfo.ZipFile & vbCr
End If
End Sub
您可以创建一个回调来以更安全的方式进行调用。像这样:
Public Sub Unzip(ByVal ZipFile As String, ByVal Destination As String, _
ByVal SafeCallback As Action(Of ZipInfo))
然后调用代码执行此操作:
t.Unzip("file 1", "foo", Sub (zi) TextBox1.Invoke(Sub () t_UnzipComplete(zi)))
就我个人而言,我认为调用事件处理程序会更好 - 也更常规 - 但你可以这样做。
答案 3 :(得分:0)
好的,所以这就是我想出的结果,每个人都提供了这个问题的所有信息 - 所有优秀和非常有用的答案,这有助于我找到最终的解决方案。理想情况下,我希望这是一个直接的&#34;类&#34;,但我可以为此目的接受UserControl。如果有人可以接受这个并且在课堂上做同样的事情,那肯定会赢得我的投票。现在,我真的要考虑选哪一个投票。
这是更新的Tools.vb
Imports System
Imports System.Threading
Imports System.Windows.Forms
Imports System.IO.Compression
Public Class Tools
Inherits UserControl
#Region "Zip"
Private _zip As System.IO.Compression.ZipFile
Private threadingContext As SynchronizationContext = SynchronizationContext.Current
Private Delegate Sub EventArgsDelegate(ByVal e As ZipInfo)
Public Shared Event UnzipComplete(ByVal ZipInfo As ZipInfo)
Public Shared Event ZipComplete(ByVal ZipInfo As ZipInfo)
Public Class ZipInfo
Public Property ZipFile As String
Public Property Path As String
End Class
Public Sub Unzip(ByVal ZipFile As String, ByVal Destination As String)
Dim _ZipInfo As New Tools.ZipInfo
_ZipInfo.ZipFile = ZipFile
_ZipInfo.Path = Destination
ThreadPool.QueueUserWorkItem(AddressOf ThreadUnzip, _ZipInfo)
End Sub
Public Sub Zip(ByVal Folder As String, ByVal ZipFile As String)
Dim _ZipInfo As New Tools.ZipInfo
_ZipInfo.ZipFile = ZipFile
_ZipInfo.Path = Folder
ThreadPool.QueueUserWorkItem(AddressOf ThreadUnzip, _ZipInfo)
End Sub
Private Sub ThreadUnzip(ZipInfo As Object)
If Me.InvokeRequired Then
Me.Invoke(New EventArgsDelegate(AddressOf ThreadUnzip), ZipInfo)
Else
RaiseEvent UnzipComplete(ZipInfo)
End If
End Sub
Private Sub ThreadZip(ZipInfo As Object)
If Me.InvokeRequired Then
Me.Invoke(New EventArgsDelegate(AddressOf ThreadZip), ZipInfo)
Else
RaiseEvent ZipComplete(ZipInfo)
End If
End Sub
#End Region
End Class
如果将其放在Form1.vb上,并选择/激活UnzipComplete / ZipComplete事件,您将发现它们将与UI线程交互,而无需从表单传递Sub或Invoke等。它也是通用的,这意味着它不知道您将与哪些表单元素进行交互,因此不需要显式调用,如TexBox1.Invoke()或其他特定于元素的调用。