ramesh
ramesh

Reputation: 19

How to properly interact with UI in threading in VB.NET

I'm new to VB.NET threading

As for simple testing I tried the following, which I need to smoothly fill a listbox with values. But it does not work as I expect, it hangs the interface. Please let me know what I'm doing wrong here.

Thank you.

Imports System.Threading

Public Class Form1

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    Dim Thr As Threading.Thread
    Thr = New Threading.Thread(New Threading.ThreadStart(AddressOf tprocess))
    'Thr.SetApartmentState(ApartmentState.STA)
    Thr.IsBackground = True
    Thr.Start()        
End Sub

Private Delegate Sub DoStuffDelegate()

Private Sub tprocess()

    Dim i As Integer

    For i = 0 To 20000
        If Me.InvokeRequired Then
            Me.Invoke(New DoStuffDelegate(AddressOf tprocess))
        Else
            ListBox1.Items.Add(i)
        End If
    Next

End Sub

End Class

Upvotes: 0

Views: 1396

Answers (3)

ramesh
ramesh

Reputation: 19

I tried the following way. It's almost easy for me to handle. Backgroudworker manages this situation perfectly well.

Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork

    Dim i As Integer

    For i = 1 To 20000
        BackgroundWorker1.ReportProgress((i / 20000) * 100, i)
        Threading.Thread.Sleep(1)
    Next
End Sub

Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
    ProgressBar1.Value = e.ProgressPercentage
    ListBox1.Items.Add(e.UserState)
End Sub

Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
    MsgBox("Complete")
End Sub

Upvotes: -1

Hans Passant
Hans Passant

Reputation: 942538

When you write code to create a thread then you always have to worry about the kind of bugs that threading can cause. They are very hard to diagnose, the only decent way to address them is to know they exist and to write the code carefully so you know how to avoid them.

The most common threading bugs are threading races, deadlock and firehose bugs. You have the 1st and the 3rd bug in your code. You are complaining about the 3rd. Very quickly: the threading race bug is using Me.InvokeRequired. You have no guarantee that it is still true when the Me.Invoke() statement executes. This goes wrong when the user closes the window while your thread is still running. When you try to fix this problem you'll get to see what the 2nd bug looks like. But you are not there yet.

The firehose bug is the Me.Invoke() call. Very fast, takes less than a microsecond of work for the worker thread, you do it 20000 times at a very high rate. It is however another thread that must actually do the work of adding the item, your UI thread. That is not fast, it not only has to add the item but it also needs to repaint the control. Many microseconds.

While this goes on, your UI thread is burning 100% core, trying to keep up with the relentless rate of invoke requests. Working as hard as it can to add items to the listbox. Something has to give, while it is doing this it is not taking care of the lower priority jobs it has to do. Painting and responding to user input. In effect, your UI looks completely frozen. You can't see it paint anymore and trying to, say, close the window doesn't work. It isn't actually dead, it is hard at work.

Takes a while, probably a few handful of seconds, give or take. Until the worker thread finishes its for() loop and stops slamming the UI thread with invoke requests. And everything turns back to normal.


A firehose bug like this is pretty fundamental. The only way to fix it is to call Invoke() less often or at a lower rate. Note how putting Thread.Sleep(50) after the Invoke() call instantly fixes it. But of course that slows down your worker thread a lot. You call Invoke() less often by using AddRange() instead of Add(), adding (say) 1000 items at a time. Which is the proper fix but now it becomes fairly pointless to still try to update the listbox from the worker thread. Might as well do it with a single AddRange() call. The quickest way.

Upvotes: 2

Matt Wilko
Matt Wilko

Reputation: 27342

Try changing:

Thr = New Threading.Thread(New Threading.ThreadStart(AddressOf tprocess))

to this:

Thr = New Threading.Thread(AddressOf tprocess)

ThreadStart will start that thread immediately

Upvotes: 0

Related Questions