可靠地更新winforms应用程序中的UI线程

时间:2016-06-17 10:43:06

标签: .net vb.net multithreading winforms

我有一个长期运行的机器学习程序,我在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

1 个答案:

答案 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

计数器会随着这些变化顺利更新。