1

我正在尝试将我的程序制作成一个多线程应用程序,但我遇到了我在以下代码中记录的一对障碍。非常感谢我能获得的任何帮助以使其正常运行,因此我可以将此存根扩展为我现有应用程序的更有效版本。

感谢您对此事的任何建议。- 亚伦

Imports System.Threading

Public Class frmMain

    ''' <summary>Initializes the multithreaded form</summary>

    Private Sub Initialize() Handles MyBase.Load

        AddThread(AddressOf Update_UI)

        running = True

        For Each Thread In ThreadPool

            Thread.IsBackground = True

            Thread.Start()

        Next

    End Sub



    ''' <summary>Terminates the multithreaded form</summary>

    Protected Overrides Sub Finalize() Handles MyBase.FormClosing

        running = False

        For Each Thread In ThreadPool

            Thread.Join()

            Thread = Nothing

        Next

    End Sub



    ''' <summary>Adds a worker thread to the ThreadPool</summary>

    ''' <param name="pointer">The AddressOf the function to run on a new thread.</param>

    Private Sub AddThread(ByRef pointer As System.Threading.ParameterizedThreadStart)

        Dim newthread As Integer

        If ThreadPool Is Nothing Then newthread = 0 Else newthread = ThreadPool.GetUpperBound(0) + 1

        ReDim Preserve ThreadPool(newthread)

        ThreadPool(newthread) = New Thread(pointer)

    End Sub



    ''' <summary>Updates the User Interface</summary>

    Private Sub Update_UI()

        'HELP: The commented out lines in this subroutine make the program work incorrectly when uncommented.

        'HELP: It should echo 'output' to the titlebar of frmMain, but it also makes the form unresponsive.
        'HELP: When I force the form to quit, the 'termination alert' does not trigger, instead the application hangs completely on Thread.Join (see above).
        'HELP: If I remove DoEvents(), the form is unable to be closed...it simply goes unresponsive.  Shouldn't the multithreading keep us from needing DoEvents()?



        'If Me.InvokeRequired Then

        '    Me.Invoke(New MethodInvoker(AddressOf Update_UI))

        'Else

            While running
                Dim output As String = System.DateTime.Now + " :: Update_UI() is active!"

                Debug.Print(output)
                'Application.DoEvents()

                'Me.Text = output

            End While

            Debug.Print(System.DateTime.Now + " :: Termination signal recieved...")

        'End If

    End Sub

    Delegate Sub dlgUpdate_UI()



    Private ThreadPool() As Thread

    Private running As Boolean

End Class
4

3 回答 3

2

是的,你尝试过的都不能正常工作。您正确地确定了需要使用 Control.Invoke() 在主线程上运行 Me.Text 分配。这就是问题所在:

  • 您的 Invoke() 调用使整个方法在主线程上运行。它将开始执行循环并且永远不会退出。您的表单变得紧张,因为它不能再做任何其他事情,例如重新绘制标题栏以显示更改的文本或响应用户输入
  • DoEvents 调用使表单恢复活力,但现在您遇到了一个新问题:用户可以关闭窗口并且您的代码继续运行。running标志永远不会设置为 false ,因此程序不会停止。用户界面虽然消失了。代码通常会轰炸 ObjectDisposedException 但在您的特定情况下不会, Text 属性存储在私有变量中
  • 您可以更改代码,以便仅 Me.Text 分配在主线程上运行。但是现在您遇到了一个新问题:主线程将受到调用请求的打击,并且不再执行其常规(低优先级)职责。它会紧张。根本问题是您尝试更新标题栏的速度太快了。没有意义,用户不能读得那么快。每秒更新 20 次就足够了,人眼看起来很流畅
  • 不要对这样的任务使用 Finalize() 方法,代码很容易触发 2 秒的终结器线程超时,炸毁你的程序。

请考虑使用 BackgroundWorker 类,它会处理其中一些细节。

于 2011-09-11T14:25:37.390 回答
1

正是 do while 循环消耗了你所有的周期,所以无论你使用多少线程,你都会输掉这场战斗并让处理器保持忙碌。以下内容将更适合您想要实现的目标。

Imports System.Threading

Public Class Form1

    Private t As New Timer(AddressOf DoTimer, Nothing, 1000, 1000)

    Private Sub DoTimer(ByVal state As Object)
        UpdateUi()
    End Sub

    ''' <summary>Updates the User Interface</summary>
    Private Sub UpdateUi()
        If InvokeRequired Then
            Invoke(New DlgUpdateUi(AddressOf UpdateUi))
        Else
            Dim output As String = DateTime.Now & " :: Update_UI() is active!"
            Debug.Print(output)
            Text = output
        End If
    End Sub

    Delegate Sub DlgUpdateUi()

    Private Sub Form1_FormClosing(sender As Object, e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
        t.Dispose()
    End Sub
End Class
于 2011-09-11T07:35:10.383 回答
0

如果我说过一次,我已经说过一百万次了。使用Invoke,虽然在许多情况下很有用,但被滥用和过度使用。如果您只想向用户显示工作线程的进度,那么使用Invoke并不总是最好的选择。而且它看起来也不是最好的选择。

相反,将您分配给的状态文本发布到output可以通过 UI 线程访问的变量中。然后使用 aSystem.Windows.Forms.Timer以更合理的速率定期轮询其值......也许每 1 秒左右。该Tick事件已在 UI 线程上运行,因此您可以立即开始使用此值通过操作各种 UI 控件向最终用户显示。

字符串很容易在线程之间传递,因为它们是不可变的,这意味着它们本质上是线程安全的。您真正需要担心的唯一一件事是确保 UI 线程看到发布到共享变量的最新引用。在 C# 中,您将为此使用volatile关键字。在 VB 中,您可以Thread.VolatileRead从 UI 线程和Thread.VolatileWrite工作线程中使用。当然,如果您更愿意将读取和写入包装在SyncLock完全可以接受的格式中。

于 2011-09-12T02:32:04.353 回答