AWestEng
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.
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
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.
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
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.
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());
}
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
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.
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... =)
But if Application.DoEvents() is NOT being used then we shouldn't worry about it though... =)
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?
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.
The threads window is helpful from the debug windows menu choices.
ASKER
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.RunW orkerCompl etedEventA rgs)
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
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.RunW
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
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
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?
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?
ASKER
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!