开发者

Why doesn't this multithreaded VB.NET (2010 Express Edition) program work properly?

I'm trying to make a program of mine into a multithreaded application, but I've hit a pair of snags that I documented in the following code. Any help that I can get with this to make it behave properly would be greatly appreciated so I can expand this stub into a more efficient version of my existing application.

Thank you for any advice you have on this matter. - Aaron

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 开发者_如何学Python'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


Yes, none of what you tried can work properly. You correctly identified the need to use Control.Invoke() to run the Me.Text assignment on the main thread. This is what is going wrong:

  • Your Invoke() call makes the entire method run on the main thread. It will start executing the loop and never exit. Your form goes catatonic since it can't do anything else anymore, like repaint the caption bar to show the changed text or respond to user input
  • The DoEvents call makes the form come back alive but now you've got a new problem: the user can close the window and your code keeps running. The running flag will never be set to false so the program won't stop. The user interface is gone though. Code would normally bomb on an ObjectDisposedException but not in your specific case, the Text property is stored in a private variable
  • You could alter the code so that only the Me.Text assignment runs on the main thread. But now you've got a new problem: the main thread will get pummeled by invoke request and doesn't get around to doing its regular (low priority) duties anymore. It goes catatonic. The essential problem is that you are trying to update the caption bar way too fast. There's no point, the user cannot read that fast. Update 20 times per second is plenty and looks smooth to the human eye
  • Do not use the Finalize() method for tasks like this, the code can easily trigger the 2 second finalizer thread time-out, bombing your program.

Do consider using the BackgroundWorker class, it takes care of some of these details.


It's the do while loop that is burning up all your cycles so you are loosing the battle and keeping the procesor busy no matter how many threads you use. Something like the following will be better suited for what you are trying to achieve.

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


If I have said once I have said it a million times. Using Invoke, while useful in many situations, is abused and way overused. If all you want to do is have the progress of the worker threads displayed to a user then using Invoke is not always the best option. And it does not look like the best option here either.

Instead, publish the status text you are assigning to output into a variable that can be accessed via the UI thread. Then use a System.Windows.Forms.Timer to periodically poll its value at a more reasonable rate...maybe every 1 second or so. The Tick event already runs on the UI thread so you can immediately begin using this value to display to the end user by manipulating the various UI controls.

Strings are really easy to pass around from thread to thread because they are immutable which means they are inherently thread-safe. The only thing you really have to worry about is making sure the UI thread sees the most recent reference published to the shared variable. In C# you would use the volatile keyword for this. In VB you can use Thread.VolatileRead from the UI thread and Thread.VolatileWrite from the worker thread. Of course, if you are more comfortable wrapping the reads and writes in a SyncLock that is perfectly acceptable as well.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜