Search code examples
vb.netwinformsbackgroundworker

VB.Net Winform Application based on BackgroundWorker Thread concept - UI update issue


Team,
I have build a VB.Net windows application which does uploads data into database and basically updates two controls:
1. A textbox which is constantly updated with one line per database record upload.
2. A label which keeps track of the count of database record uploaded.

I have used BackgroundWorker thread concept, where the thread's bgwWorker_DoWork() method contains the business logic for upload and bgwWorker_ProgressChanged() updates the 2 UI controls based on uploads.

But the issue I am facing is that I do not get complete updates on both the UI controls. Sometimes the thread bypasses update of textbox and sometimes of label. I could resolve this issue by adding System.Threading.Thread.Sleep(25) before each UI control update code. Is this correct way of solving the issue? OR is there something I am missing?

Kindly suggest.

Below is the code in both these methods:

Private Sub bgwWorker_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgwWorker.DoWork
    .................
    .................
    'Updates database record related update in textbox
    System.Threading.Thread.Sleep(25)
    updater.eventName = "UpdateStatusBox"
    updater.errorMessageToLog = String.Empty
    updater.errorMessageToLog += GetErrorMessage(dataTable(rowNumber)("Name").ToString(), ExceptionData)
    bgwWorker.ReportProgress(1, updater)

    .................
    .................
    'Updates Status Count in LABEL  
    System.Threading.Thread.Sleep(25)
    updater.eventName = "UpdateStatusBar"
        updater.successCount = successCount.ToString()
        updater.failureCount = failureCount.ToString()
        bgwWorker.ReportProgress(2, updater)
End Sub

Private Sub bgwWorker_ProgressChanged(ByVal sender As System.Object, ByVal e As ProgressChangedEventArgs) Handles bgwWorker.ProgressChanged
        Dim updater As UIUpdater = TryCast(e.UserState, UIUpdater)

    ..........................................
        If updater.eventName = "UpdateStatusBar" Then
            UpdateStatusBar(updater.successCount, updater.failureCount)
        ElseIf updater.eventName = "UpdateStatusBox" Then
            txtUpdates.Text = txtUpdates.Text & updater.errorMessageToLog
        End If
        .....................................
End Sub

Solution

  • I'm almost positive that your problem is your instance of the UIUpdater object called updater. This object appears to be declared globally and is thus shared between calls.

    Omitting a little bit of code this is what you have:

    updater.eventName = "UpdateStatusBox"
    bgwWorker.ReportProgress(1, updater)
    
    updater.eventName = "UpdateStatusBar"
    bgwWorker.ReportProgress(2, updater)
    

    Although you call ReportProgress() linearly, it doesn't fire your ProgressChanged event immediately nor does it block until that method completed. To do so would defeat the purpose of threading if you think about it.

    To put it another way, you have a global object that you are setting a property on. You then say "when someone gets a chance, do something with this". You then change a property on that global object and sometimes this happens before "someone has done something" happens.

    The solution is either to create two global variables, one for each possible event or to just create an instance variable when needed. I'm not sure that its thread safe to use a global variable the way you are so I would recommend just creating an instance variable. In fact, the state object you pass to ReportProgress could just be a string.