我正在尝试更新一个在模块的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.
答案 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
现在我们可以更好地分离关注点:"数据库"只知道细粒度的数据库操作,表单知道如何将这些数据库操作放在一起并在必要时自行更新。由于使用了Thread
和BeginInvoke
,它仍然很丑陋。 .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