vb.net - VB.NET (WinForms) 中带参数的安全线程池队列

标签 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

(向此表单添加一个多行文本框和一个按钮以进行演示)

工具.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 上,将 9 个项目添加到线程池
  • 在每个线程完成时(顺序无关),引发一个提升到 Form1 的事件

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

最佳答案

如果您想确保不直接了解您的 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 线程上创建该类的实例,它的 SomethingHappened 事件就会在 UI 线程上引发。如果没有 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

关于vb.net - VB.NET (WinForms) 中带参数的安全线程池队列,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25375142/

相关文章:

python - pymysql连接线程安全吗? pymysql游标线程安全吗?

java - Spring 组件类必须是线程安全的

vb.net - 如何在 vb.net 的 MVC 4 中创建并返回带有 Controller 的 Excel 文件?

mysql - Mysql 中索引超出数组异常

c# - 当第一个表单已加载时,如何将第二个表单作为对话框打开?

C# - 具有单个消费者线程的多个生产者线程

mysql - VB : Fill multiple textboxes with data from MySQL DB

vb.net - 如何在vb.net中迭代字典?

c# - BackgroundWorker 中仅更新了 2 个进度条中的 1 个

c# - Debug.WriteLine(string, params object[]) 是一种方法,但像类型一样使用