vb.net 从另一个线程中的模块更新标签
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/24712169/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Updating label from module in another thread
提问by jaminben
I'm trying to update a label which is running in the UI thread from a Module... my code works fine if its within the Main Form but I'd like to try and keep my code tidy by not having it all within the Main Form and splitting it out into Modules.
我正在尝试从模块更新在 UI 线程中运行的标签......如果它在主窗体中,我的代码工作正常,但我想尝试通过不在主窗体中保持我的代码整洁主窗体并将其拆分为模块。
So if I have the below in my Main Form it works:
因此,如果我的主表单中有以下内容,则它可以工作:
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
I'd like to move all of it to a Module except for:
我想将所有内容移到一个模块中,除了:
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
But when I do the Invoke.Required never returns a true value which then doesn't update my label on my Main Form.
但是当我执行 Invoke.Required 时,它永远不会返回一个真值,该值不会更新我的主表单上的标签。
What am I doing wrong?
我究竟做错了什么?
Thanks
谢谢
Ben
本
UPDATED
更新
My Main Form Contains this:
我的主表单包含以下内容:
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
And My Module:
和我的模块:
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
This however causes an error:
然而,这会导致错误:
Invoke or BeginInvoke cannot be called on a control until the window handle has been created.
回答by Kirill Shlenskiy
It's amusing that the very first version of the code you posted was also the best.
有趣的是,您发布的代码的第一个版本也是最好的。
Rather than going into a lengthy discussion about how things could be improved let me present a rewrite of your current code which would achieve the main goal and get you up and running, quickly:
与其就如何改进事情进行冗长的讨论,不如让我介绍一下您当前代码的重写,这将实现主要目标并让您快速启动和运行:
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
This will work because now you're passing around a concrete reference to an instance of form_Main. My problem with this approach, however, is that your module should really have no notion of form_Mainin the first place. My initial recommendation was going to be progress reporting via Progress/IProgress, but it's only appropriate in cases where you're processing a collection of some kind, whereas you're working with disparate database operations, so a better design would be as follows:
这将起作用,因为现在您正在传递对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
Now we have better separation of concerns: the "database" only knows about the fine-grained database operations, and the form knows how to put those database operations together and update itself when necessary. It's still ugly though due to Threadand BeginInvokeuse. .NET 4.5 provides better mechanisms of composing asynchronous operations which allow us to rewrite the above as follows:
现在我们有了更好的关注点分离:“数据库”只知道细粒度的数据库操作,而表单知道如何将这些数据库操作放在一起并在必要时更新自身。尽管由于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
EDIT
编辑
If you absolutely must report progress from a long-running operation, since .NET 4.0 System.Progress(Of T)/IProgress(Of T)is the recommended way of doing so in a caller-agnostic fashion. Note that it's a generic type so it is ultimately up to you what it is exactly that you want to report throughout the processing - and while the convention is Int32denoting progress percentage, you could also use something completely arbitrary like Strings, for example.
如果您绝对必须报告长时间运行的操作的进度,因为 .NET 4.0System.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

