安全的ThreadPool在VB.NET中使用参数排队(WinForms)

时间:2014-08-19 03:09:08

标签: vb.net winforms thread-safety threadpool

我知道如何使用BackgroundWorker(WinForms设计器中的gui对象),并手动实例化将自定义事件提升到UI的线程,但是,我在查找如何使用ThreadPool对象(最简单的形式)时遇到了一些麻烦处理将事件提升到表单以进行“安全”UI操作。

示例如下:

Form1.vb的

    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

(为演示添加多行文本框和此表单的按钮)

Tools.vb

    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

此代码应该执行的操作如下:

  • 在Button1_Click上,向ThreadPool添加9个项目
  • 在每个线程完成时(顺序无关紧要),引发一个提升为Form1的事件

在Form1上引发的事件应该是UI安全的,因此我可以使用传递给Textbox中的ZipCompleted / UnzipCompleted事件的信息。这应该是通用的,这意味着引发事件的函数应该是可重用的,并且不会直接调用表单。 (也就是说,我不想在Tools.vb中调用Form1.vb上的特定元素的“自定义”子或函数。这应该是通用的,可以通过将类添加到我的项目然后在下面输入任何“自定义”表单代码来重复使用引发的事件(例如,当引发Button1_Click时,即使它是线程化的,其他表单交互也不是Button1对象/类的一部分 - 它们由编码器写入用户单击时引发的事件。

4 个答案:

答案 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()或其他特定于元素的调用。