Want to protect your cyber security and still get fast solutions? Ask a secure question today.Go Premium

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 879
  • Last Modified:

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

0
AWestEng
Asked:
AWestEng
  • 6
  • 4
  • 3
2 Solutions
 
mastooCommented:
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
 
Mike TomlinsonMiddle School Assistant TeacherCommented:
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
 
mastooCommented:
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
Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
Mike TomlinsonMiddle School Assistant TeacherCommented:
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
 
mastooCommented:
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
 
Mike TomlinsonMiddle School Assistant TeacherCommented:
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
 
AWestEngAuthor Commented:
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
 
mastooCommented:
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
 
AWestEngAuthor Commented:
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
 
mastooCommented:
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
 
AWestEngAuthor Commented:
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
 
AWestEngAuthor Commented:
Thx guys for all the great help . :)
0
 
mastooCommented:
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

Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

  • 6
  • 4
  • 3
Tackle projects and never again get stuck behind a technical roadblock.
Join Now