Reputation: 43
I have a long running machine learning program that I run in the background in parallel using all cores in a winforms application. Periodically I update the UI to report on progress.
The machine seems to choose fairly random times to execute the message pump on the UI thread. Sometimes I get no updates for several minutes, sometimes I get a message every time I send one.
I've tried every which way to make this reliable including standard invoking from the background thread, using progress reporting from a backgroundworker, using a timer on the UI thread to collect information and display it, reducing the maximum number of threads that can run in parallel, messing with thread priorities etc. The only way I've found to reliably get an update is to add a console to the winforms program and output progress to the console. For some reason, that is 100% reliable, but it is a real hack and looks messy.
Does anyone know of a way to force the ui thread to be updated reliably?
As requested: Here is the most basic code that replicates the error. Create a form with a label called label1. The code is an attempt to update the label every millionth iteration.
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
Upvotes: 3
Views: 396
Reputation: 42494
The problem is that the Parallel.ForEach
insists on running the all tasks on the UI thread. One way I got this changed was by calling the long_running_process
from a BackgroundWorker.DoWork
, like so:
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
Notice that I had to provide the Label1
as a parameter because otherwise the InvokeRequired always returned false.
The changed long_running_process
looks like this:
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
The counter updates smoothly with these changes.
Upvotes: 2