Solved

BackgroundWorker thread, Execute methods in UI thread

Posted on 2008-10-20
13
864 Views
Last Modified: 2013-11-07
Hi

I have a class that inherits the backgroundworker and has all the methods in it that will be executed for this thread, so all ok there, this works fine,...

The problem I got is when the execution of the thread is done, then I like to execute a methods in the UI thread.

So here is a code example.

Check the WorkCompleted method, here I want to execute the UpdateGUI Sub.

The only problem is that I could have a lot of threads that wants to start this method at the same time, so I need some solution that will work if this will be the case.
Public Class Server

    Inherits System.ComponentModel.BackgroundWorker

 

 

    Private m_serverAction As ServerActions

 

    Public Sub New(ByVal activeServer As String)

 

        MyBase.New()

 

        Me.WorkerReportsProgress = False

        Me.WorkerSupportsCancellation = False

 

        AddHandler Me.DoWork, AddressOf Work

        AddHandler Me.RunWorkerCompleted, AddressOf WorkCompleted

        AddHandler Me.ProgressChanged, AddressOf BGWProgress

 

        Me.m_activeServer = activeServer

 

    End Sub

 

 

    Public Sub Work()

        Me.RunWorkerAsync()

    End Sub

 

 

    Public Property ServerAction() As ServerActions

        Get

            Return Me.m_serverAction

        End Get

        Set(ByVal value As ServerActions)

            Me.m_serverAction = value

        End Set

    End Property

 

 

    Private Sub Work(ByVal sender As System.Object, ByVal doWorkArgs As System.ComponentModel.DoWorkEventArgs)

 

        Dim callingBackgroundWorker As System.ComponentModel.BackgroundWorker = CType(sender, System.ComponentModel.BackgroundWorker)

        Dim blnStartCalulation As Boolean = False

 

        Select Case m_serverAction

            Case Start

                StartServer(sender, doWorkArgs)

            Case [Stop]

                StopServer(sender, doWorkArgs)

        End Select

    End Sub

 

 

    Private Sub WorkCompleted(ByVal sender As System.Object, _

    ByVal completedArgs As System.ComponentModel.RunWorkerCompletedEventArgs)
 

'// Here I need to cast an event or something that will start the 

UpdateGUI method in the UI thread
 

    End Sub

 

 

    Private Sub BGWProgress(ByVal sender As System.Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs)

    End Sub

 

    Private Sub StartServer(ByVal sender As System.Object, ByVal doWorkArgs As System.ComponentModel.DoWorkEventArgs)

    End Sub

 

 

    Private Function StopServer(ByVal sender As System.Object, ByVal doWorkArgs As System.ComponentModel.DoWorkEventArgs) As Boolean

    End Function

 

 

    Private Sub myProcess_Exited(ByVal sender As Object, ByVal e As System.EventArgs)

    End Sub

 

    Private Sub m_shutDownProcess_Exited(ByVal sender As Object, ByVal e As System.EventArgs)

    End Sub

 

End Class

 

    Public Class frmMain

 

        Private Sub Start()

            Dim serverThread As New Server(m_activeServer)

 

            serverThread.ServerAction = Start()

 

            m_ThreadList.Add(serverThread, m_activeServer)

 

            serverThread.Work()

        End Sub

 

        Private Sub [Stop]()

            Dim serverThread As Server = CType(m_ThreadList.Item(m_activeServer), Server)

 

            serverThread.ServerAction = [Stop]

 

            serverThread.Work()

        End Sub
 

         Public Sub UpdateGUI()

    ....... Od dome GUI updates, buttons text etc

        End Sub

    End Class

Open in new window

0
Comment
Question by:AWestEng
  • 6
  • 4
  • 3
13 Comments
 
LVL 21

Expert Comment

by:mastoo
Comment Utility
I believe you'd be ok because they would block on the WorkCompleted call since it runs on the gui thread.  You could test with a quick example by setting a breakpoint inside your workcompleted method.  First call, freeze the thread on the breakpoint.  Continue and see what your other thread does.
0
 
LVL 85

Expert Comment

by:Mike Tomlinson
Comment Utility
If you want to ensure that each WorkCompleted() event waits for the others to finish before executing then use SyncLock:
Public Class Server

    Inherits System.ComponentModel.BackgroundWorker
 

    Private Shared UpdateSyncObject As New Object
 

    Private Sub WorkCompleted(ByVal sender As System.Object, ByVal completedArgs As System.ComponentModel.RunWorkerCompletedEventArgs)

        SyncLock Server.UpdateSyncObject

            frmMain.UpdateGUI()

        End SyncLock

    End Sub
 

End Class

Open in new window

0
 
LVL 21

Expert Comment

by:mastoo
Comment Utility
The WorkCompleted doesn't need thread synchronization as BackgroundWorker magically takes care of that.  Attached is a snippet to illustrate.  Interestingly, the output is:

Gui thread = 10
DoWork = 7
DoWork = 7
Completed begins = 10
Completed ends = 10
Completed begins = 10
Completed ends = 10

Note the 5 second delay in the RunWorkerCompleted method so the second thread attempts to call it before the first call completes.  From the output messages, you can see the second thread is blocked until the first call finishes.
    private void button1_Click(object sender, EventArgs e)

    {

      Debug.WriteLine("Gui thread = " + Thread.CurrentThread.ManagedThreadId.ToString());

      backgroundWorker1.RunWorkerAsync();

      backgroundWorker2.RunWorkerAsync();

    }
 

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)

    {

      Debug.WriteLine("DoWork = " + Thread.CurrentThread.ManagedThreadId.ToString());

    }
 

    private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)

    {

      Debug.WriteLine("Completed begins = " + Thread.CurrentThread.ManagedThreadId.ToString());

      System.Threading.Thread.Sleep(5000);

      Debug.WriteLine("Completed ends = " + Thread.CurrentThread.ManagedThreadId.ToString());

    }

Open in new window

0
 
LVL 85

Accepted Solution

by:
Mike Tomlinson earned 250 total points
Comment Utility
I don't think that is a valid example mastoo.

It is blocked because you have put the main UI thread to SLEEP.  Of course it can't become re-entrant if the UI isn't responding to ANY messages at all!   =\

I agree that it should work, BUT ONLY if you don't use Application.DoEvents().

To show what kind of mayhem can ensue with DoEvents(), consider this output:
bgw1 work start
bgw2 work start
bgw1 work done
bgw1 result RunWorkerCompleted Entered
bgw1 result 10/20/2008 3:59:06 PM
bgw1 result 10/20/2008 3:59:07 PM
bgw1 result 10/20/2008 3:59:08 PM
bgw1 result 10/20/2008 3:59:09 PM
bgw2 work done
bgw1 result 10/20/2008 3:59:10 PM
bgw2 result RunWorkerCompleted Entered
bgw2 result 10/20/2008 3:59:11 PM
bgw2 result 10/20/2008 3:59:12 PM
bgw2 result 10/20/2008 3:59:13 PM
bgw2 result 10/20/2008 3:59:14 PM
bgw2 result 10/20/2008 3:59:15 PM
bgw2 result 10/20/2008 3:59:16 PM
bgw2 result 10/20/2008 3:59:17 PM
bgw2 result 10/20/2008 3:59:18 PM
bgw2 result 10/20/2008 3:59:19 PM
bgw2 result 10/20/2008 3:59:20 PM
bgw2 result RunWorkerCompleted Done!
bgw1 result RunWorkerCompleted Done!

Here is the code that produced it:
Public Class Form3
 

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

        BackgroundWorker1.RunWorkerAsync()

        BackgroundWorker2.RunWorkerAsync()

    End Sub
 

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

        Debug.Print("bgw1 work start")

        e.Result = "bgw1 result"

        Debug.Print("bgw1 work done")

    End Sub
 

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

        Debug.Print("bgw2 work start")

        System.Threading.Thread.Sleep(5000)

        e.Result = "bgw2 result"

        Debug.Print("bgw2 work done")

    End Sub
 

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

        Debug.Print(e.Result & " RunWorkerCompleted Entered")

        Dim targettime As DateTime = DateTime.Now.AddSeconds(10)

        While targettime.Subtract(DateTime.Now).TotalMilliseconds > 0

            System.Threading.Thread.Sleep(1000)

            Debug.Print(e.Result & " " & DateTime.Now)

            Application.DoEvents()

        End While

        Debug.Print(e.Result & " RunWorkerCompleted Done!")

    End Sub
 

End Class

Open in new window

0
 
LVL 21

Expert Comment

by:mastoo
Comment Utility
Thanks for the response Idle_Mind.  No question, yielding to the message pump can cause reentrant behavior.

Maybe my lack of vb knowledge is showing, but I thought SyncLock would allow reentrant behavior on a single thread and thus do nothing to prevent the DoEvents problem?  That was the point I was addressing, somewhat obtusely.
0
 
LVL 85

Expert Comment

by:Mike Tomlinson
Comment Utility
I think you are right...SyncLock wouldn't work here because it is already on the same thread once it gets to the RunWorkCompleted() event...it would have to be moved further back; perhaps into the Work() method.

But if Application.DoEvents() is NOT being used then we shouldn't worry about it though...  =)
0
How to improve team productivity

Quip adds documents, spreadsheets, and tasklists to your Slack experience
- Elevate ideas to Quip docs
- Share Quip docs in Slack
- Get notified of changes to your docs
- Available on iOS/Android/Desktop/Web
- Online/Offline

 
LVL 1

Author Comment

by:AWestEng
Comment Utility
hi guys!
Two bright minds at work :)
So guys what method should I use,  that is really safe, I will not use Applicaiton.DoEvents in the server class but it is used in the frmMain class.
I really appreciate your expertise on this matter.
how the thread works where can I get see that, which window?
0
 
LVL 21

Expert Comment

by:mastoo
Comment Utility
I'd rely on the WorkCompleted already being synchronized, leaving just the message pump (DoEvents, etc.) reentrancy problem.  I tend to just use a logical guard variable to avoid the DoEvents problem.  At the start of the workcompleted, test a variable bool bCallInProgress and if it is already true just exit the method.  That works fine as long as the WorkCompleted method just needs to update the gui and not necessarily respond to every call, since the guard variable will lead to some calls being skipped.  If there is something from every call that needs to be tracked, use a common variable somewhere - maybe a member variable on your form or a static variable on your derived class.  Because that variable is shared by threads, you would probably use SyncLock to synchronize access to it.

The threads window is helpful from the debug windows menu choices.
0
 
LVL 1

Author Comment

by:AWestEng
Comment Utility
Ok,
 I added the frmMain.UpdateGUI into the WorkCompleted methods, that seams to work just fine.
oki but do I need to use synclock in the Public Sub UpdateGUI method it acceses a private member in the frmMain class?
the logical guard you are talking about, do mean this?
   Private Sub WorkCompleted(ByVal sender As System.Object, ByVal completedArgs As System.ComponentModel.RunWorkerCompletedEventArgs)
       if bCallInProgress  = False Then
            frmMain.bCallInProgress  = True
            frmMain.UpdateGUI()
            frmMain.bCallInProgress  = False
        End If
    End Sub

Public Class frmMain
   Public bCallInProgress  as boolean = False
End Class
0
 
LVL 21

Assisted Solution

by:mastoo
mastoo earned 250 total points
Comment Utility
Exactly.  That works in this situation because it is guarding against reentrant calls, not multiple threads (because WorkCompleted always runs on the same gui thread).  No synclock required (it wouldn't help with this anyway).

Not to confuse things too much, but I should point out one further thing.  If you have frequent threads completing, this approach works well because it doesn't try to respond to every thread completion.  It skips some.  Nicer performance, but technically if your last two threads complete at about the same time your gui might not get updated by the last one to one.  To assure every completion causes a gui update you could either go with a more complicated approach, or go low tech with a refresh timer (calls WorkCompleted every 5 seconds or whatever).

When you're all done, it is interesting to put breakpoints in WorkCompleted and watch how the calls get processed :-)
0
 
LVL 1

Author Comment

by:AWestEng
Comment Utility
I already got  a timer thread that execute the UpdateGUI every 5 seconds. :)
 But in the update GUI thread I also have some updating of private members, so do I need to synclock the stuff in UpdateGUI methods then?
and I must also implement the guard in the timer method to, don't I?
0
 
LVL 1

Author Closing Comment

by:AWestEng
Comment Utility
Thx guys for all the great help . :)
0
 
LVL 21

Expert Comment

by:mastoo
Comment Utility
The only synclock you'd need would be if the worker threads are updating anything external to them.  For instance, a worker thread updating some member variable on the form  But usually what you'll do is the worker thread keeps its data internal, and then the gui can access that data after the thread completes - no sync required.  Probably instead of a guard in the timer you'd put the guard in the updategui or workcompleted so all code funnels through the single guard variable during the gui update.  Good luck!
0

Featured Post

IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

Join & Write a Comment

This article describes relatively difficult and non-obvious issues that are likely to arise when creating COM class in Visual Studio and deploying it by professional MSI-authoring tools. It is assumed that the reader is already familiar with the cla…
Real-time is more about the business, not the technology. In day-to-day life, to make real-time decisions like buying or investing, business needs the latest information(e.g. Gold Rate/Stock Rate). Unlike traditional days, you need not wait for a fe…
It is a freely distributed piece of software for such tasks as photo retouching, image composition and image authoring. It works on many operating systems, in many languages.
You have products, that come in variants and want to set different prices for them? Watch this micro tutorial that describes how to configure prices for Magento super attributes. Assigning simple products to configurable: We assigned simple products…

744 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

18 Experts available now in Live!

Get 1:1 Help Now