从另一个线程中的模块更新标签

时间:2014-07-12 10:50:26

标签: vb.net

我正在尝试更新一个在模块的UI线程中运行的标签...如果我的代码在主窗体中运行正常,但是我想尝试通过不使用它来保持我的代码整洁所有在主表格内并将其分成模块。

因此,如果我的主表单中有以下内容,则可以使用:

Private threadingExecuteManualScan As Thread

Public Sub toolStripItem_Run_Manual_Scan_Click(sender As Object, e As EventArgs) Handles toolStripItem_Run_Manual_Scan.Click

        threadingExecuteManualScan = New Thread(AddressOf executeManualScanThread)
        threadingExecuteManualScan.IsBackground = True
        threadingExecuteManualScan.Start()

End Sub

Delegate Sub SetTextDelegate(ByVal textString As String)

Private Sub updateTextBox(ByVal stringValue As String)

        Dim textDelegate As New SetTextDelegate(AddressOf updateTextBox)
        form_Main.BeginInvoke(textDelegate, stringValue)

End Sub

Public Sub executeManualScanThread()

    updateTextBox("Update Label With This String")

End Sub

我想将所有内容移到模块中,除了:

Public Sub toolStripItem_Run_Manual_Scan_Click(sender As Object, e As EventArgs) Handles toolStripItem_Run_Manual_Scan.Click

        threadingExecuteManualScan = New Thread(AddressOf executeManualScanThread)
        threadingExecuteManualScan.IsBackground = True
        threadingExecuteManualScan.Start()

End Sub

但是当我这样做时,Invoke.Required永远不会返回一个真值,然后不会在我的主表单上更新我的标签。

我做错了什么?

由于

已更新

我的主要表格包含:

Public Class form_Main

Delegate Sub SetTextDelegate(ByVal args As String)
Private threadingExecuteManualScan As Thread

Public Sub toolStripItem_Run_Manual_Scan_Click(sender As Object, e As EventArgs) Handles toolStripItem_Run_Manual_Scan.Click

        threadingExecuteManualScan = New Thread(AddressOf executeManualScanThread)
        threadingExecuteManualScan.IsBackground = True
        threadingExecuteManualScan.Start()

End Sub

Public Sub updateTextBox(ByVal stringValue As String)

        Dim textDelegate As New SetTextDelegate(AddressOf updateTextBox)
        me.BeginInvoke(textDelegate, stringValue)

End Sub

End Class

和我的模块:

Module module_Helper_Threading

Public Sub executeManualScanThread()

        'Some Database Work
        form_Main.SetTextBoxInfo("Report Back - Step 1")
        'Some More Database Work
        form_Main.SetTextBoxInfo("Report Back - Step 2")
        'etc

End Sub

End Module

然而这会导致错误:

Invoke or BeginInvoke cannot be called on a control until the window handle has been created.

1 个答案:

答案 0 :(得分:4)

您发布的代码的第一个版本也是最好的,这很有趣。

不要进行冗长的讨论,讨论如何改进事物,而是让我重新编写您当前的代码,以实现主要目标并快速启动并运行:

Imports System.Threading

Public Class form_Main

  Private Sub toolStripItem_Run_Manual_Scan_Click() Handles toolStripItem_Run_Manual_Scan.Click
    Dim t As New Thread(Sub() module_Helper_Threading.executeManualScanThread(Me))

    t.IsBackground = True
    t.Start()
  End Sub

  Public Sub SetTextBoxInfo(stringValue As String)
    Me.BeginInvoke(Sub() Me.TextBox.Text = stringValue)
  End Sub

End Class

Module module_Helper_Threading

  Public Sub executeManualScanThread(form_Main As form_Main)

    'Some Database Work
    Thread.Sleep(1000)

    form_Main.SetTextBoxInfo("Report Back - Step 1")

    'Some More Database Work
    Thread.Sleep(1000)

    form_Main.SetTextBoxInfo("Report Back - Step 2")
    'etc

  End Sub

End Module

这样可行,因为现在您正在传递对form_Main实例的具体引用。但是,我对这种方法的问题是,你的模块首先应该没有form_Main的概念。我最初的建议是通过Progress/IProgress进行进度报告,但它仅适用于您处理某种类型的集合的情况,而您正在使用不同的数据库操作,所以更好的设计如下:

Imports System.Threading

Public Class form_Main

  Private Sub toolStripItem_Run_Manual_Scan_Click() Handles toolStripItem_Run_Manual_Scan.Click
    Dim t As New Thread(AddressOf Me.RunManualScan)

    t.IsBackground = True
    t.Start()
  End Sub

  Private Sub RunManualScan()
    ' We know this will be running on a background thread.
    Dim workResult1 = DatabaseWork.SomeWork()

    Me.SetTextBoxInfo("Report Back - Step " & workResult1)

    Dim workResult2 = DatabaseWork.OtherWork()

    Me.SetTextBoxInfo("Report Back - Step " & workResult2)
  End Sub

  Public Sub SetTextBoxInfo(stringValue As String)
    Me.BeginInvoke(Sub() Me.TextBox.Text = stringValue)
  End Sub

End Class

' You could use a Module, but it
' pollutes IntelliSense more than Class.
Public NotInheritable Class DatabaseWork

  Public Shared Function SomeWork() As Int32
    Thread.Sleep(1000)

    Return 1
  End Function

  Public Shared Function OtherWork() As Int32
    Thread.Sleep(1000)

    Return 2
  End Function

End Class

现在我们可以更好地分离关注点:"数据库"只知道细粒度的数据库操作,表单知道如何将这些数据库操作放在一起并在必要时自行更新。由于使用了ThreadBeginInvoke,它仍然很丑陋。 .NET 4.5提供了更好的组合异步操作的机制,允许我们按如下方式重写上述内容:

Imports System.Threading
Imports System.Threading.Tasks

Public Class form_Main

  Private Sub toolStripItem_Run_Manual_Scan_Click() Handles toolStripItem_Run_Manual_Scan.Click
    Me.ExecuteManualScan()
  End Sub

  ' Note the Async modifier.
  Private Async Sub ExecuteManualScan()
    ' The delegate passed to Task.Run executes on
    ' a thread pool (background) thread. Await'ing
    ' a task transitions us back to the original thread.
    ' Note that it is good practice to use Task.Run for
    ' CPU-bound work, but since we're stuck with blocking
    ' database operations, it will have to do in this case.
    Dim workResult1 = Await Task.Run(AddressOf DatabaseWork.SomeWork)

    ' Note the lack of BeginInvoke - we're already on the UI thread.
    Me.TextBox.Text = "Report Back - Step " & workResult1

    ' Note that the delegate is declared slightly differently.
    ' While functionally similar to the first call, this version
    ' allows you to pass arguments to the method if necessary.
    Dim workResult2 = Await Task.Run(Function() DatabaseWork.OtherWork())

    Me.TextBox.Text = "Report Back - Step " & workResult2
  End Sub

End Class

修改

如果您绝对必须报告长时间运行的进度,那么因为.NET 4.0 System.Progress(Of T)/IProgress(Of T)是以与调用者无关的方式这样做的推荐方式。请注意,它是一种通用类型,因此最终取决于您想要在整个处理过程中报告的内容 - 虽然约定是Int32表示进度百分比,但您也可以使用完全随意的内容比如String s,例如。

Imports System.Threading
Imports System.Threading.Tasks

Public Class form_Main

  Private Sub toolStripItem_Run_Manual_Scan_Click() Handles toolStripItem_Run_Manual_Scan.Click
    Me.ExecuteManualScan()
  End Sub

  Private Async Sub ExecuteManualScan()
    ' Ensure that the next scan operation cannot
    ' be started until this one is complete by
    ' disabling the relevant UI elements.
    Me.toolStripItem_Run_Manual_Scan.Enabled = False

    Try
      Me.TextBox.Text = "Starting ..."

      ' When you create an instance of Progress, it captures
      ' the current SynchronizationContext, and will raise
      ' the ProgressChanged event on that context, meaning
      ' that if it's created on the UI thread, the progress
      ' handler callback will automatically be marshalled back
      ' to the UI thread for you, so you no longer need Invoke.
      Dim progress As New Progress(Of Int32)

      ' Update the UI when progress is reported.
      AddHandler progress.ProgressChanged,
        Sub(s, progressPercentage) Me.TextBox.Text = String.Format("Progress: {0}%.", progressPercentage)

      Dim workResult = Await Task.Run(Function() DatabaseWork.LongWork(progress))

      Me.TextBox.Text = "Result: " & workResult
    Finally
      Me.toolStripItem_Run_Manual_Scan.Enabled = True
    End Try
  End Sub

End Class

Public NotInheritable Class DatabaseWork

  Public Shared Function LongWork(progress As IProgress(Of Int32)) As Int32
    Dim progressPercentage = 0

    For i = 0 To 100 - 1
      ' Simulate some work.
      Thread.Sleep(10)

      progressPercentage += 1

      progress.Report(progressPercentage)
    Next

    Return 42
  End Function

End Class