Solved

BackgroundWorker thread, Execute methods in UI thread

Posted on 2008-10-20
13
865 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
ID: 22760089
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
ID: 22760958
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
ID: 22762459
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
ID: 22763113
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
ID: 22763320
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
ID: 22763394
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
DevOps Toolchain Recommendations

Read this Gartner Research Note and discover how your IT organization can automate and optimize DevOps processes using a toolchain architecture.

 
LVL 1

Author Comment

by:AWestEng
ID: 22765481
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
ID: 22766997
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
ID: 22813314
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
ID: 22813531
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
ID: 22813939
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
ID: 31507747
Thx guys for all the great help . :)
0
 
LVL 21

Expert Comment

by:mastoo
ID: 22814580
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

3 Use Cases for Connected Systems

Our Dev teams are like yours. They’re continually cranking out code for new features/bugs fixes, testing, deploying, testing some more, responding to production monitoring events and more. It’s complex. So, we thought you’d like to see what’s working for us.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

Parsing a CSV file is a task that we are confronted with regularly, and although there are a vast number of means to do this, as a newbie, the field can be confusing and the tools can seem complex. A simple solution to parsing a customized CSV fi…
A long time ago (May 2011), I have written an article showing you how to create a DLL using Visual Studio 2005 to be hosted in SQL Server 2005. That was valid at that time and it is still valid if you are still using these versions. You can still re…
This Micro Tutorial hows how you can integrate  Mac OSX to a Windows Active Directory Domain. Apple has made it easy to allow users to bind their macs to a windows domain with relative ease. The following video show how to bind OSX Mavericks to …
Internet Business Fax to Email Made Easy - With  eFax Corporate (http://www.enterprise.efax.com), you'll receive a dedicated online fax number, which is used the same way as a typical analog fax number. You'll receive secure faxes in your email, f…

911 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

21 Experts available now in Live!

Get 1:1 Help Now