Link to home
Start Free TrialLog in
Avatar of AWestEng
AWestEngFlag for Sweden

asked on

BackgroundWorker thread, Execute methods in UI thread

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

Avatar of mastoo
mastoo
Flag of United States of America image

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.
Avatar of Mike Tomlinson
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

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

ASKER CERTIFIED SOLUTION
Avatar of Mike Tomlinson
Mike Tomlinson
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
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.
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...  =)
Avatar of AWestEng

ASKER

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?
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.
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
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
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?
Thx guys for all the great help . :)
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!