Post

Working with Background Workers

In .NET framework, Visual Basic on August 29, 2009 by Omkarnath Tagged: , , , ,

Using Background worker class to do your work in separate thread :-

First we need to include System.ComponentModel namespace-

Imports System.ComponentModel

Now we need to do some declarations-

    Dim WithEvents worker As New BackgroundWorker
    Dim ListBox1 As New ListBox
    Dim Button1 As New Button
    Dim ProgressBar1 As New ProgressBar

Lets initialize these things and add them in our form so we get ready to go..

Just paste this code-

        Private Sub buildForm()

        With Me
            .Size = New Size(350, 350)
        End With

        With ListBox1
            .Size = New Size(300, 250)
            .Location = New Point(10, 10)
        End With

        With ProgressBar1
            .Size = New Size(215, 25)
            .Location = New Point(10, 260)
            .Maximum = 20
            .Step = 1
        End With

        With Button1
            .Size = New Size(75, 25)
            .Location = New Point(235, 260)
            .Text = "Go"
        End With

        Me.Controls.Add(ListBox1)
        Me.Controls.Add(Button1)
        Me.Controls.Add(ProgressBar1)

        worker.WorkerReportsProgress = True
        worker.WorkerSupportsCancellation = True

    End Sub

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        buildForm()
    End Sub

Thats enough time spent on Form, lets move to the BackgroundWorker.

What we want to accomplist here is to Add number 1 to 20 in listbox1 on button click. and for some reason I want to do it in background thread.
Background worker has a DoWork event. When you call BackgroundWorker.RunWorkerAsync() method, a subroutine that handles DoWork event of BackgroundWoker is fired.
That means we need a sub routine that will have some work to do. Lets make it-

    Private Sub worker_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) Handles worker.DoWork
        For i = 0 To 20
            ListBox1.Items.Add(i.ToString)
            Thread.Sleep(100)
        Next
    End Sub

There’s it. Now if we call worker.RunWorkerAsync() in button1.Click, it should fire this event.

    Private Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs) Handles Button1.Click
        worker.RunWorkerAsync()
    End Sub

Paste it and press F5 key to start debugging your application. After clicking a button, you’ll get InvalidOperationException at ListBox1.Items.Add(i.ToString) line.
Reason being our try to access controls created by/on different thread. Why this is invalid ? Because it can create a conflict ! In very simple words, nowadays we have dual core processors which can handle 2 threads at a time. If both the threads try to access same control at the same time, there’s a clash. Now, how to handle this exception or rather how to avoid this exception.. and there’re 2 ways to get rid of this.
First is to simply set Control.CheckForIllegalThreadCalls=False. Which is NOT RECOMMENDED. and other is to use Delegates to invoke methods.

Lets start with delegates. Declare a delegate sub.

    Private Delegate Sub addItem(ByVal value As Integer)

We also need a subroutine that will add number in ListBox1.

    Private Sub addListBoxItems(ByVal value As Integer)
        ListBox1.Items.Add(value.ToString)
    End Sub

Now we need to change our worker_DoWork() subroutine. We need to invoke method of Listbox1 using the delegate we have decalred.
This is how our modified code should be like-

   Private Sub worker_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) Handles worker.DoWork
        For i = 0 To 20
            If ListBox1.InvokeRequired Then
                ListBox1.Invoke(New addItem(AddressOf addListBoxItem), New Object() {i})
            Else
                ListBox1.Items.Add(i.ToString)
            End If
            Thread.Sleep(100)
        Next
    End Sub

Paste the code and check if it works fine. It does :-)

Now we need to know progress of our background worker. Background worker class performs a ProgressChanged event as soon as we it reports progress. Write a sub routine that will handle progress changed event of our worker. We also need to check for progress and perform step of progress bar.

    Private Sub worker_ProgressChanged(ByVal sender As Object, ByVal e As ProgressChangedEventArgs) Handles worker.ProgressChanged
        ProgressBar1.PerformStep()
    End Sub

This event will get fired when we worker reports progress. For that we need to put this line just before Next statement of for loop in worker_DoWork sub routine.

           worker.ReportProgress(1)

We can also report value of loop variable ‘i’ in this context

           worker.ReportProgress(i)

Hence, our worker_DoWork() sub routine will now look like this-

    Private Sub worker_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) Handles worker.DoWork
        For i = 0 To 20
            If ListBox1.InvokeRequired Then
                ListBox1.Invoke(New addItem(AddressOf addListBoxItem), New Object() {i})
            Else
                ListBox1.Items.Add(i.ToString)
            End If
            worker.ReportProgress(i)
            Thread.Sleep(100)
        Next
    End Sub

Start debugging to see the effect !

and here’s our final complete code for reference-


Imports System.ComponentModel

Public Class Form1
    Dim WithEvents worker As New BackgroundWorker

    Dim ListBox1 As New ListBox
    Dim WithEvents Button1 As New Button
    Dim ProgressBar1 As New ProgressBar

    Private Sub buildForm()
        With Me
            .Size = New Size(350, 350)
        End With

        With ListBox1
            .Size = New Size(300, 250)
            .Location = New Point(10, 10)
        End With

        With ProgressBar1
            .Size = New Size(215, 25)
            .Location = New Point(10, 260)
            .Maximum = 20
            .Step = 1
        End With

        With Button1
            .Size = New Size(75, 25)
            .Location = New Point(235, 260)
            .Text = "Go"
        End With

        Me.Controls.Add(ListBox1)
        Me.Controls.Add(Button1)
        Me.Controls.Add(ProgressBar1)

        worker.WorkerReportsProgress = True
        worker.WorkerSupportsCancellation = True

    End Sub

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        buildForm()
        Control.CheckForIllegalCrossThreadCalls = False
    End Sub

    Private Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs) Handles Button1.Click
        worker.RunWorkerAsync()
    End Sub

    Private Delegate Sub addItem(ByVal value As Integer)

    Private Sub addListBoxItem(ByVal value As Integer)
        ListBox1.Items.Add(value.ToString)
    End Sub

    Private Sub worker_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) Handles worker.DoWork
        For i = 0 To 20
            If ListBox1.InvokeRequired Then
                ListBox1.Invoke(New addItem(AddressOf addListBoxItem), New Object() {i})
            Else
                ListBox1.Items.Add(i.ToString)
            End If
            worker.ReportProgress(i)
            Thread.Sleep(500)
        Next
    End Sub

    Private Sub worker_ProgressChanged(ByVal sender As Object, ByVal e As ProgressChangedEventArgs) Handles worker.ProgressChanged
        ProgressBar1.PerformStep()
    End Sub

End Class

Thanks

One Response to “Working with Background Workers”

  1. [...] like report progress event. When using threads you need to apply somewhat similar logic. Refer my post about Background Workers to know more about it. In this post I’ll explain using threads for doing a similar task as [...]

Leave a Reply