Question controls unresponsive

Zexor

Well-known member
Joined
Nov 28, 2008
Messages
520
Programming Experience
3-5
I am using a backgroundworker to populate an imagelist with images from the web. It is just a loop that feeds new name and url to the following line with Application.DoEvents() in between.

VB.NET:
ImageList1.Images.Add(Name, System.Drawing.Bitmap.FromStream(Net.HttpWebRequest.Create(URL).GetResponse.GetResponseStream))

But when the backgroundworker is working, the controls are pretty unresponsive. Am i doing something wrong?
 
You shouldn't have the DoEvents call because the whole point of the BackgroundWorker is to perform a task in the background while the UI thread is free to user input and update the display. That said, if your system only has one processor and the background task is processor intensive, the UI thread can still suffer.
 
backgroundworker

I am on an i7 and it was doing the same thing without the do event. Shouldn't background worker be in the back and not effect the ui? It only effect the ui of that program, not other programs.
 
It would display a bunch of images after awhile, then back to thinking and more images.

VB.NET:
Dim Files(,) As String



ImageList1.Images.Clear()
BackgroundWorker1.RunWorkerAsync()




    Delegate Sub SetListView1Callback(i As Integer, retry As Integer)
    Private Sub SetListView1(i As Integer, retry As Integer)
        If ListView1.InvokeRequired Then
            Dim d As New SetListView1Callback(AddressOf SetListView1)
            Me.Invoke(d, New Object() {i, retry})
        Else
            Try
                ImageList1.Images.Add(Files(3, i), System.Drawing.Bitmap.FromStream(Net.HttpWebRequest.Create(Files(1, i)).GetResponse.GetResponseStream))
                ListView1.Items(i).ImageIndex = i
            Catch ex As Exception
                If retry < 3 Then SetListView1(i, retry + 1)
            End Try
        End If
    End Sub


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


        For i As Integer = 0 To ListView1.Items.Count - 1
            SetListView1(i, 0)


            If BackgroundWorker1.CancellationPending Then
                e.Cancel = True


                Exit Sub
            End If
        Next


    End Sub
 
Last edited:
You seem to have missed the point of the BackgroundWorker altogether. Firstly, it is supposed to do work in the background and secondly it is supposed to allow you to update the UI without using Invoke. you're not using either feature there.

Do you know what Invoke does? It marshals a method call to the UI thread. You start your BackgroundWorker and run a loop on the background thread but you don't actually do any work on the background thread in that loop. Each iteration you immediately switch back to the UI thread and do all the work there.

First of all, get rid of that SetListView method. Inside the loop in the DoWork event handler is where you get the Image. Once you've got the Image and added it to the ImageList, then you call ReportProgress. That will raise the ProgressChanged event on the Ui thread. You handle that event and update the UI in the event handler.

Here's an example of using a BackgroundWorker properly:

Using the BackgroundWorker Component
 
Thanks i really didnt understand invoke before. Now the backgroundworker is working smoothly. but i had to unlink the imagelist1 with listview1.largeimagelist in order to not get the cross-thread error for listview1. So now i only see the images once all of them are done. Is there a way to show them one by one as they come in?

VB.NET:
Dim Files(,) as string

ImageList1.Images.Clear()
ListView1.LargeImageList = Nothing
BackgroundWorker1.RunWorkerAsync()

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

        For i As Integer = 0 To ListView1.Items.Count - 1

            ImageList1.Images.Add(Files(0, i), System.Drawing.Bitmap.FromStream(Net.HttpWebRequest.Create(Files(2, i)).GetResponse.GetResponseStream))

            BackgroundWorker1.ReportProgress(i * 100 / (ListView1.Items.Count - 1), i)

            If BackgroundWorker1.CancellationPending Then
                e.Cancel = True

                Exit Sub
            End If
        Next


End Sub


Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged

        ListView1.Items(e.UserState).ImageIndex = e.UserState
        tspb1.Value = e.ProgressPercentage


End Sub


Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted

        ListView1.LargeImageList = ImageList1

End Sub
 
Last edited:
I wasn't sure about whether adding an item to the ImageList on the background thread would be an issue but apparently it is. That's not problem though. The long-running task that you're trying to perform is downloading the Image, not adding it to the ImageList. The actual adding part can be done in the ProgressChanged event handler too. By the way, this shouldn't be in the DoWork event handler either:
VB.NET:
For i As Integer = 0 To [B][U]ListView1.Items.Count[/U][/B] - 1
Simply don't make reference to any controls in the DoWork event handler. Something like this is the way to go:
VB.NET:
BackgroundWorker1.RunWorkerAsync(ListView1.Items.Count - 1)
VB.NET:
Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
    Dim upperBound = CInt(e.Argument)

    For I = 0 To upperBound
        Dim img = System.Drawing.Bitmap.FromStream(Net.HttpWebRequest.Create(Files(2, i)).GetResponse.GetResponseStream)

        BackgroundWorker1.ReportProgress(i, img)

        If BackgroundWorker1.CancellationPending Then
            e.Cancel = True

            Return
        End If
    Next
End Sub

Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
    Dim index = e.ProgressPercentage
    Dim img = DirectCast(e.UserState, Image)

    ImageList1.Images.Add(Files(0, index), img)
    ListView1.Items(index).ImageIndex = index
End Sub
 
That worked great, thanks. About the invoke, when would you use something like this?

VB.NET:
    Delegate Sub SetListView1Callback(i As Integer, retry As Integer)
    Private Sub SetListView1(i As Integer, retry As Integer)
        If ListView1.InvokeRequired Then
            Dim d As New SetListView1Callback(AddressOf SetListView1)
            Me.Invoke(d, New Object() {i, retry})
        Else
            
        End If
    End Sub
 
That is for when you are executing code on a secondary thread and need to update the UI but, as I said, the BackgroundWorker is designed to let you avoid that. The idea with the BackgroundWorker is that you only call methods and handle events, which even quite inexperienced developers are familiar with. If you have created a Thread object and called its Start method or called QueueUserWorkItem on the ThreadPool class then you will not have the infrastructure provided by the BackgroundWorker so you have to handle more of the low-level detail yourself. You could do what you're doing here without a BackgroundWorker and then you'd have to use Invoke with a delegate instead of calling ReportProgress and handling ProgressChanged.
 
backgroundworker

If you need to pass more than 2 variables to the progresschanged, would you pass an array on userState?
 
'userState' is type Object specifically so that it can be used for any type of object. You should pass whatever is most appropriate for the situation, e.g. an Integer, a String, an array, a Tuple, a DataSet, a class of your own or whatever.
 
Back
Top