p2p.wrox.com Forums

p2p.wrox.com Forums (http://p2p.wrox.com/index.php)
-   Pro VB.NET 2002/2003 (http://p2p.wrox.com/forumdisplay.php?f=74)
-   -   Multi-Threading Issue (http://p2p.wrox.com/showthread.php?t=56585)

jwebb April 12th, 2007 09:27 AM

Multi-Threading Issue
 
Let me be very general.

I'm creating an application that needs to run a process continuously until the user kills it. So run the continuous process on a separate thread until the user clicks a 'Kill' button right? The problem is that this process needs to do a lot of updating to controls on the main UI. Scouring the internet for information has led me to believe that it is bad practice to update UI controls created on the main thread from a separate thread.

How can I give the user a way to kill a continual process that constanly updates controls on the UI? Apparently 'background worker in Studio 2005 handles this easily, but I am limited to 2003.

Thanks for any help.


planoie April 12th, 2007 10:02 AM

It's not just bad practice, but it doesn't work. The runtime will throw an error when you try to update a control created by another thread.

It's been a while since I've done this, but it involves using the "BeginInvoke" method of the form class you want to update. Basically, BeginInvoke() takes a delegate to the method you want to invoke (this would be a method on the form that will update the controls). This causes the class on which BeginInvoke is called (the form) to perform the actual invocation of the method, thus crossing the thread boundary back to the one that owns the controls.

-Peter

jwebb April 12th, 2007 11:40 AM

I'm actually pretty unclear on how this works. Can anyone think of a simple example or post a link to simple example to understand the gist of marshalling?


planoie April 12th, 2007 01:42 PM

Maybe I can explain with an analogy:

Imagine your program is a lunch Diner. The key players (and what they relate to in your program):

Cook (the form running in the main program thread): makes the food (updates the UI)
Waiter (background worker thread): takes customer's orders (runs your continuous task)

The window to the kitchen is the thread boundary.

Cook spawns off Waiters. The waiters go around doing their tasks in the dining room. When a waiter gets an order, he write it down (prepares data for UI update) and goes to the window. The waiter calls to Cook "Hey Cook! Make this order, here it is." and hands the order ticket thru the window (crossing the boundary).

In your code, the Cook is the main form (running in the parent thread) so your code looks like this:

this.BeginInvoke(new delegate(StartCooking), new object[] {Order});

To map the Diner scenario to the code:

Hey Cook! -> this
Make -> .BeginInvoke( )
this order, -> new delegate(StartCooking)
here it is. -> new object[] {Order}

BeginInvoke will call the "StartCooking" method and pass it the "Order". The ACTUAL call to "StartCooking" is made by an object in the parent thread, versus the object in the child thread that called "BeginInvoke".

In your code, "StartCooking" will be "UpdateUI". Your worker process will invoke it with the necessary information to update the UI controls. However, the call will actually be made from the main thread which permits control updates.

I hope this helps explain how it works. I have a program that does this, but it would take a while to extract all the non-important code from the relevant stuff to show you. However, I got it from an article somewhere, so you should be able to find something similar.

-Peter

jwebb April 12th, 2007 02:28 PM

I appreciate the analogy to an everyday situation. The idea behind threading is brought out plainly in your diner scenario. At the risk of making myself look too much like a beginner, I have to ask for a bit more help.

The concepts being solid, it's the syntax that gets me. Especially when it comes to delegates. Consider the following elementary problem:

1. A form with a textbox (displaying count) and two buttons (start, kill)
2. If the user clicks on the start button, an infinite loop is commenced say,

dim i as integer
i=1
while i>0
i=i+1
end while

3. Obviously, the user has a long wait in front of them. But let's say that while the programmer wasn't quick-witted enough to avoid the infinite loop, he was code-savvy enough to put the incrementing process on a separate thread. Now we can say that the start button event handler is used to initiate the creation of a secondary thread where the above code will now be processed.

4. The kill button affords the user the opportunity to .abort the loop. Problem solved.

5. But suppose while the variable is incrementing on it's separate thread, we want the counter to update in the textbox (perhaps using the thread sleep command for 3 second pauses)? This necessitates invoking the original thread, which is where I lose track of the syntax.

I believe that if someone can show me the proper syntax for this procedure, I can take it from there.

One more question - is the moving back and forth between threads a time consuming process?

Again thanks for your help.

planoie April 12th, 2007 07:50 PM

For a beginner, you have a good grasp of what's going on.

I just found this article that explains in much greater detail the specifics of how all this works:

http://www.codeproject.com/csharp/begininvoke.asp

In your step # 5, you have the exact right idea. The "worker" thread has to tell the form thread to invoke the form control update method.

To refresh my own memory I whipped up a sample app. I'll post the relevant part of the code. Hopeful it will get you going in the right direction. Here is the important parts of a sample winform app:
Code:

    Private _objThread As Thread

    Private Sub cmdStart_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdStart.Click
        cmdStart.Enabled = False
        _objThread = New Thread(AddressOf ContinuousProcess)
        _objThread.Start()
        cmdStop.Enabled = True
    End Sub

    Private Sub cmdStop_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdStop.Click
        cmdStop.Enabled = False
        _objThread.Abort()
        _objThread = Nothing
        cmdStart.Enabled = True
    End Sub

    Private Sub ContinuousProcess()
        Dim i As Integer = 0
        While True
            i += 1
            Me.BeginInvoke(New CounterUpdateDelegate(AddressOf UpdateCounter1), New Object() {i})
            Me.BeginInvoke(New CounterUpdateDelegate(AddressOf UpdateCounter2), New Object() {i})
            Thread.Sleep(1000)
        End While
    End Sub

    Delegate Sub CounterUpdateDelegate(ByVal count As Integer)

    Private Sub UpdateCounter1(ByVal count As Integer)
        txtCount1.Text = count.ToString
    End Sub
    Private Sub UpdateCounter2(ByVal count As Integer)
        txtCount2.Text = count.ToString
    End Sub

Delegates can be tricky to understand at first, hope this clarifies it. Think of a delegate statement in the code as a definition of a method signature. In my example:

Delegate Sub CounterUpdateDelegate(ByVal count As Integer)

defines "CounterUpdateDelegate" as a method that will take an integer and return nothing. I can have many methods that conform to that signature definition. In the example above, I intentionally created two to illustrate this point.

When I want to use one of the methods that conform to my delegate type, I create an instance of the delegate type defined ("CounterUpdateDelegate") and put into it the address of the method I wish to actually use. This is what is known as a Call Back. In this case, the logic inside of BeginInvoke is going to "Call Back" to the method I handed it through the callback delegate.

-Peter

jwebb April 14th, 2007 01:08 PM

Peter,

Thanks a million for the basic code. This is the easy to understand item I've been looking everywhere for.

If you have some time, maybe you can answer a few more questions for me:

1. If my background thread needs to pass back say ten or twelve values, should I expect the processing time to get really bogged down?

2. Do I need to have a separate 'begininvoke' statement for each control that I need to update on the UI or is there a more wholistic approach?

3. When do I need to start worrying about synchroniztion and 'synclock'?

4. In the background thread, can you refer directly to values associated with a control, or do you have to pass these values to the thread? Put more simply - while we know you can't write from the background thread, can you at least read values in from the main thread? If not, how do you pass the values?

I apologize if I'm loading you up with questions, but to this point you've been my best resource. Thanks again for your help.





planoie April 15th, 2007 07:19 PM

1. Because of the way they constructed the BeginInvoke method, you can pass it anything you wish. The second argument is simply an object array so you can create an array of values you need to pass to the method that will be called:

   Me.BeginInvoke(New CounterUpdateDelegate(AddressOf UpdateCounter1), New Object() {value1, value2, value3, ...})

Alternatively, and in many cases easier to deal with, create a class that will contain all the "arguments". This can be used in much the same way as .NET uses event args classes. Your class would have properties for all the values you need to pass to the other thread, and you pass an instance of the object as a single argument to the BeginInvoke call. Of course, by doing this you have to consider thread safety.

   Me.BeginInvoke(New CounterUpdateDelegate(AddressOf UpdateCounter1), New Object() {myFormUpdateArguments})


2. The method delegate called by the BeginInvoke call can update all controls as needed.

3. I'm not really sure.

4. There's no problem here. Yes, you can not update controls created by another thread, but there's nothing preventing you from READing values from another thread's controls.


One misconception that I'm still fighting is that different threads are in different scopes of a program. I'm not sure why I have had this idea in my head but I do. It's important to remember that several threads created by the same applications are all still within the scope of the same application. It's just that another *process* is handling the execution of some number of method calls. The point here is that secondary threads within a given program can see everything that the main thread sees (according to standard scope visibility of course). Granted, some rules need to be adhered to, such as simultaneous accessing of certain resources (like files) and restrictions on what can be updated (as we've been discussing) but otherwise, the code runs just the same as if you called it from the single main thread.

-Peter

jwebb April 22nd, 2007 01:50 PM

Peter,

New thoughts on the same application. The threading is working well for me now, except...

I would like the secondary thread to stop operating while the main thread has been invoked to update GUI controls. Ordinarily this would be done with the .join() method, correct? If I understand that method definition properly, the thread using the .join method halts until the target thread of the join method terminates. But in reality, the main thread doesn't terminate until the application terminates, right? So is it impossible to .join the gui thread? Or more simply, how can I halt the secondary thread until the main thread has finished updating the GUI. Also, sample syntax if you can.

As always, your help (and anyone else's, of course) would be invaluable.

Jason


planoie April 23rd, 2007 06:52 AM

I am not an expert on threading. I've just used it enough to get myself into trouble ;).

It would appear your description is correct. From the MSDN docs for Thread.Join Method () :
"Blocks the calling thread until a thread terminates, while continuing to perform standard COM and SendMessage pumping."

Yes, the main (UI) thread never exits until you close/exit the application.

Are you looking to *suspend* the worker thread while the GUI updates? Or do you actually want to stop it?

If you want to suspend it, I'd ask the question: Why would you want to do that? The whole point of a worker thread is so you have a thread doing the work as quickly as possible in the background while still allowing the main (UI) thread to update and access messages (i.e. button click, etc). Suspending the worker thread for UI updates will just slow things down. There may be better ways to do this, such as not ALWAYS updating the UI. Instead you update it on some time interval.

-Peter


All times are GMT -4. The time now is 06:08 PM.

Powered by vBulletin®
Copyright ©2000 - 2020, Jelsoft Enterprises Ltd.
© 2013 John Wiley & Sons, Inc.