我有一个长期运行的机器学习程序,我在winforms应用程序中使用所有核心 在后台运行 。我会定期更新UI以报告进度。
机器似乎选择相当随机的时间来在UI线程上执行消息泵。有时几分钟我没有得到任何更新,有时每次发送一条消息都会得到一条消息。
我已尝试过各种方法使其可靠,包括从后台线程进行标准调用,使用后台工作人员的进度报告,使用UI线程上的计时器收集信息并显示它,减少最大数量可以并行运行的线程,弄乱线程优先级等。我发现可靠地获得更新的唯一方法是向winforms程序添加一个控制台并将进度输出到控制台。出于某种原因,这是100%可靠的,但它是一个真正的黑客,看起来很乱。
有没有人知道强制ui线程可靠更新的方法?
按要求:这是复制错误的最基本代码。创建一个名为label1的表单。代码是每百万次迭代更新标签的尝试。
Module testmodule
delegate sub invokedelegate(txt as string)
Sub long_running_process()
Dim x(100000000) As Integer
Dim cnt As Integer
Dim syncobject As New Object
form1.Label1.Text = "started"
Parallel.ForEach(x, Sub(z)
'*** This just put in to make the processors do some work.
Dim p As New Random
Dim m As Double = p.NextDouble
Dim zzz As Double = Math.Cosh(m) + Math.Cos(m)
'*** This is the basic updating method.
SyncLock syncobject
cnt += 1
'*** Update every millionth iteration
If cnt Mod 1000000 < 1 Then
'**** This is how it is marshalled to the UI thead.
If Form1.InvokeRequired Then
Form1.BeginInvoke(New invokedelegate(AddressOf invokemethod), {cnt})
Else
Form1.Label1.Text = cnt
End If
End If
End SyncLock
End Sub)
Form1.Label1.Text = "Finished"
End Sub
Sub invokemethod(txt As String)
form1.Label1.Text = txt
End Sub
end module
答案 0 :(得分:1)
问题是Parallel.ForEach
坚持在UI线程上运行所有任务。我改变这种方法的一种方法是从long_running_process
调用BackgroundWorker.DoWork
,如下所示:
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
testmodule.long_running_process(Label1)
End Sub
End Class
请注意,我必须提供Label1
作为参数,否则InvokeRequired总是返回false。
更改后的long_running_process
如下所示:
Sub long_running_process(lbl As Label)
Dim x(100000000) As Integer
Dim cnt As Integer
Dim syncobject As New Object
If lbl.InvokeRequired Then
lbl.BeginInvoke(New MethodInvoker(Sub()
lbl.Text = "started"
End Sub), Nothing)
End If
Parallel.ForEach(x, Sub(z)
'*** This just put in to make the processors do some work.
Dim p As New Random
Dim m As Double = p.NextDouble
Dim zzz As Double = Math.Cosh(m) + Math.Cos(m)
'*** This is the basic updating method.
SyncLock syncobject
cnt += 1
'*** Update every millionth iteration
If cnt Mod 1000000 < 1 Then
'**** This is how it is marshalled to the UI thead.
If lbl.InvokeRequired Then
lbl.BeginInvoke(New invokedelegate(AddressOf invokemethod), {cnt.ToString()})
Else
lbl.Text = cnt
End If
End If
End SyncLock
End Sub)
If lbl.InvokeRequired Then
lbl.BeginInvoke(New MethodInvoker(Sub()
lbl.Text = "Finished"
End Sub), Nothing)
End If
End Sub
Sub invokemethod(txt As String)
Form1.Label1.Text = txt
End Sub
计数器会随着这些变化顺利更新。