Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/vb.net/17.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
VB.NET(WinForms)中带参数的安全线程池排队_Vb.net_Winforms_Thread Safety_Threadpool - Fatal编程技术网

VB.NET(WinForms)中带参数的安全线程池排队

VB.NET(WinForms)中带参数的安全线程池排队,vb.net,winforms,thread-safety,threadpool,Vb.net,Winforms,Thread Safety,Threadpool,我知道如何在WinForms designer中使用BackgroundWorker gui对象,以及如何手动实例化将自定义事件提升到UI的线程,但是,我在弄清楚如何使用ThreadPool对象最简单的表单来处理将事件提升到表单以进行安全UI操作时遇到了一些问题 示例如下: Form1.vb 为演示添加一个多行文本框和一个按钮 Tools.vb 此代码应执行的操作如下: 单击按钮1,向线程池添加9个项目 在每个线程完成顺序不相关的情况下,引发一个提升到Form1的事件 Form1上引发的事件应该

我知道如何在WinForms designer中使用BackgroundWorker gui对象,以及如何手动实例化将自定义事件提升到UI的线程,但是,我在弄清楚如何使用ThreadPool对象最简单的表单来处理将事件提升到表单以进行安全UI操作时遇到了一些问题

示例如下:

Form1.vb 为演示添加一个多行文本框和一个按钮

Tools.vb 此代码应执行的操作如下:

单击按钮1,向线程池添加9个项目 在每个线程完成顺序不相关的情况下,引发一个提升到Form1的事件
Form1上引发的事件应该是UI安全的,因此我可以使用传递给文本框中ZipCompleted/UnzipCompleted事件的信息。这应该是通用的,这意味着引发事件的函数应该是可重用的,并且不直接调用表单。aka,我不想在Tools.vb中使用自定义子函数或函数来调用Form1.vb上的特定元素。这应该是通用的和可重用的,方法是将类添加到我的项目中,然后在引发的事件下输入任何自定义表单代码,比如当Button1_Click被引发时,即使它是线程化的,其他表单交互不是Button1对象/类的一部分-它们由编码器写入用户单击时引发的事件。

您应该从表单注册到工具类的事件您已经定义了这些事件,当然实际事件将在非UI线程下激发,因此,它在回调期间执行的代码只能通过调用来更新UI

如果只想在Tools类中引发事件,则需要进行调用,因为您想更新UI,Tools类应该关注这一点

更改事件处理方式,如下所示:

Private Sub t_UnzipComplete(ZipInfo As Tools.ZipInfo) Handles t.UnzipComplete
   TextBox1.Invoke(Sub () t_UnzipComplete(ZipInfo))
End Sub
要从视图注册到事件:这将进入Button1\u Click事件


确保您只注册一次活动

这是否解决了您的问题

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)))

就我个人而言,我认为在事件处理程序上调用更好,也更传统,但您可以这样做。

如果您想确保一个对您的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线程上创建该类的实例,它的SomethingOccessed事件就会在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

好的,下面是我利用所有参与这个问题的人提供的信息得出的结论——所有优秀且非常有用的答案,帮助我找到了最终的解决方案。理想情况下,我希望这是一个纯类,但我可以为此接受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或调用等。它也是泛型的,这意味着它不知道您将与哪些表单元素交互,因此显式调用(如TexBox1)。不需要调用或其他特定于元素的调用。

我正在类中寻找更独立的元素。哪个类?关于引发事件,我的建议是您应该在那里做什么,我的回答解释了需要做什么。是否有一种方法可以从Tools类中调用,或者以某种方式满足要求,以便在raiseevent命令之前或期间对UI友好?不,您不想这样做。如果您只想引发事件,则需要进行调用,因为您想更新UI,Tools类不应该对此进行关注。我是否可以强制在raiseevent行而不是在事件处理程序下执行调用?@SanuelJackson-您需要对UI对象(即TextBox1)进行引用才能调用。调用。。。。从类中传递引用是不好的封装。不,您不希望将此类代码放在Tools类中,此UI代码属于UI@SanuelJackson-唯一可行的方法是传递tools类可以调用的委托,而不是实际的UI元素。然而,这仍然可能会导致代码依赖性问题。@SanuelJackson-您所引用的内容与我的建议无关。我建议你通过一项T行动
通过ools.ZipInfo调用解压方法,而不是引发事件。您从UI传递的委托需要进行调用。请尝试。关于子集合中的e值的问题出现了。。。这是从哪里来的,因为我没有看到任何可接受的参数。Sube部分是Lambda表达式的声明。SynchronizationContext.Post的第一个参数是SendOrPostCallback类型,它是对具有Object类型的一个参数的子对象的委托。OnSomethingAppended方法没有该签名,因此不能直接使用它来创建委托。您需要另一个具有适当签名的方法,并且使用Lambda比声明命名方法更简单。我将添加一个额外的代码示例,该示例使用一个命名方法来更清楚地显示正在发生的事情。您必须重新编写整个代码才能使其发挥任何作用。我们给了你非常简单的答案。@TMcKeown,没有。您只需要向Tools类添加一些简短的方法。坦率地说,OnZipComplete和OnUnzipComplete方法应该已经存在了。我确切地知道使用您的解决方案需要什么,这是没有必要的,他只需要处理调用,即使使用您的解决方案,当SyncContext不是UI线程时,他也需要处理调用。您不需要将其设置为UserControl。。。。为什么不在UnzipComplete事件上以???@TMcKeown的形式调用Invoke?我希望在控件/类中100%地保持线程功能,而不需要在类的每个实例上调用任何额外的东西。想象一下,如果每次点击按钮,你都要额外写5行代码。。。。我觉得这个类应该是自包含的,并且根据OP的要求可以识别UI。本质上,这个类的唯一目的是处理后台内容,而我的UI代码仍然只是UI逻辑。想象一下,如果你不得不将每个类都转换成用户控件,因为程序员不想正确编码;但说真的,这是编程,这是你必须做的。如果有人想使用您生成事件的类,那么您可以正确地处理它们。。。这就是它的工作原理,任何事件生成类都会遇到同样的问题。我同意你的观点。需要其他代码的一个示例是在更新大量对象时锁定/解锁元素。我只是不觉得UI是处理从后台线程到UI线程的转换的地方,这可以以可重用的方式来完成——编码的一个关键点是使代码更加可移植/可重用。我不这么认为,听起来好像在不应该的地方有代码,而让这个类成为用户控件会让它更具可移植性?祝你好运
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
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
    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