"Jason Black [MSFT]" <jasonbl@microsoft.com> a écrit dans le message de
news: 41efe61e$1@news.microsoft.com...[color=blue]
> While Bruno is entirely correct that you shouldn't be trying to update UI
> controls from a worker thread, I disagree that dealing with this situation
> is really all that hard. It's kind of hard the first time you do it, but
> after that it's really not a big deal. That is, it's sort of a lot to get
> your head around if you've never worked with delegates or BeginInvoke
> before, but the concepts reduce down to a pretty small amount of actual
> code that you need to write.[/color]
The difficult thing here is not really writing the code. Once you 've got a
good example, it is actually quite simple (and your post explains it very
well).
The difficult part is to find your way through the jungle if this is your
first step into multi-thread programming. And I have the impression that
Jeff tried to figure out the solution by himself rather than by cloning an
example. So, he read about multi-threading, monitors, etc. and he came up
with a solution that would have worked with any free-threaded toolkit (AWT,
Swing, SWT) but that does not work with WinForms because of the "appartment
threading" issue (and he had to introduce a timeout hack to get it working
most of the time). In short, Jeff walked through the jungle but did not find
the right path.
Actually, once you know about "appartement threading" and about
Control.Invoke/BeginInvoke, the whole thing becomes simpler because these
calls are thread-safe, and the pure UI code is executed by a single thread.
This is relatively easy to understand for someone who has been doing
multi-threading for a while, but for someone who makes his first steps, this
whole thing (learning about threads, monitors, and then discovering
apartment threading and Control.Invoke, and trying to figure out if monitors
still make sense in this context, etc.) sounds rather overwhelming to me.
Maybe some kind of "expert system" would help here:
Question: what objects does your thread interact with?
a) only objects created by the thread itself
b) objects created by the thread itself and UI objects created by the UI
thread
c) objects created by the thread itself and non UI objects created by
other threads
d) a mixture of the above.
Answer:
a) You are on the safe side (but check twice that you are really in this
case, make sure you did not forget a static variable or a an object that is
shared by the two threads, or a static method that is called by both threads
and accesses a static variable without any synchronization, etc.).
b) Learn about Control.Invoke and Control.BeginInvoke. Don't worry about
monitors, unless you are in case d (check this twice, as in case a)
c) Learn about monitors, the C# lock keyword, deadlocks, etc.
d) Make sure you understand the whole thing.
Also, beware of COM components. They usually use apartment threading models.
So, replace "UI objects" by "apartment threaded COM components" in all the
above.
This is why I said that Microsoft makes it harder. Other multi-threaded
systems (like Java) don't mix free threading and apartment threading, and
you only need to deal with cases a) and c). And it is a bit easier to find
one's way through the jungle.
Bruno.
[color=blue]
>
> What you need to do is to define an event that your worker thread can
> throw when it needs to send data back to the UI thread, create an event
> handler to process that event, and in the event handler do a little bit of
> work to make sure that you're in the correct thread before you update your
> UI controls. To update a text box (or the .Text field of any control,
> really), for example, you'll need to do something like this:
>
> // First, declare a delegate called "StringEventHandler" to hold the
> function
> // prototype information which connects events with their handlers. The
> // delegate is the mechanism by which the compiler enforces type-safety
> // between events and their handlers.
> public delegate void StringEventHandler(object worker, StringEventArgs e);
>
> // Oh, look, we just referenced something called "StringEventArgs".
> That's
> // a class we need to make, derived from EventArgs, to hold the actual
> string
> // data we want the worker thread to pass back to the UI thread:
> public class StringEventArgs : EventArgs {
> public string TheString; // surely you'll come up with a better name
> than this.
> public StringEventArgs(string S) { TheString = s; }
> }
>
> // Now we go ahead and declare an event called "OnStringEvent", which is
> of
> // type "StringEventHandler". This guarantees that only functions which
> are expecting
> // a StringEventArgs object will be allowed to subscribe to the event.
> public event StringEventHandler OnStringEvent;
>
> // Your event handler is really just a function which wraps the "are we on
> the right
> // thread? If not, switch" logic around whatever UI updating you wanted
> to do in
> // the first place.
> private void MyStringEventHandler(object worker, StringEventArgs e) {
> if(myTextBox.InvokeRequired) {
> this.BeginInvoke(new StringEventHandler(MyStringEventHandler),
> new object[]{worker,e});
> } else {
> myTextBox.Text = e.TheString;
> }
> }
>
> // Of course, somewhere you need to make sure that your event handler is
> // actually subscribed to the event itself (this is a good thing to do in
> your
> // form's constructor, after the InitializeComponent() call.
> OnStringEvent += new StringEventHandler(MyStringEventHandler);
>
> // and finally, when it's time, the worker thread needs to raise the event
> in
> // order to trigger whatever event handlers have subscribed to it:
> if(OnStringEvent != null) // don't do anything if nobody has subscribed.
> OnStringEvent(this, new StringEventArgs(someString));
>
> I remember when I was learning this stuff there were two parts that hurt
> my brain the most. The first was understanding how the delegate related
> to the event and the event handler. When my C/C++ addled brain figured
> out "oh, delegates are how we make type-safe function pointers", then that
> part was ok. It took me a while to figure that out, because none of the
> books I was reading just came right out and said that, but that's really
> what they're for. The "new StringEventHandler(MyStringEventHandler)" is
> just the C# object-oriented way to express the "function pointer" itself.
>
> The second hard thing was understanding what was going on with
> BeginInvoke. The job of BeginInvoke is to do some sort of internal magic
> in order to be executing in the same thread as "this" (your form's thread,
> which is the thread that created all your UI controls, and is therefore
> the right thread on which to update those controls), and then to call
> whatever function you like for you, over in that thread. To do that last
> part, BeginInvoke needs a pointer to our event handler, plus an object
> array containing the arguments that will be passed to our event handler.
> That the event handler uses BeginInvoke to call itself is largely a matter
> of convention. There are other ways this could work (the worker thread
> could use BeginInvoke to switch threads before raising the event, for
> instance, or the UI thread could use BeginInvoke to call a helper function
> which actually updates the control), but this is the pattern that WinForms
> developers have come to more or less standardize on, as it seems to
> involve the least mess and it puts the responsibility for being on the
> right thread in the right place: with the control that insists on that in
> the first place. Sort of like telling the form "well, if you don't want
> your controls to be updated from some other thread, then you do the thread
> switching. Don't bother me with that crap."
>
> To sum up, then, the runtime sequence of actions involved in getting your
> UI control updated is:
>
> 1. The worker thread decides it needs to send a string back to the UI, so
> it constructs a StringEventArgs object to hold the string, and then raises
> the event.
> 2. The event handler gets invoked, still running in the worker thread.
> 3. The event handler checks, by means of this.InvokeRequired, to see
> whether it's in the worker thread or the UI thread.
> 4. It will discover that it's still in the worker thread, and will call
> BeginInvoke with the right information so that BeginInvoke will call the
> event handler again after switching threads.
> 5. The event handler gets invoked a second time, with all the same
> arguments as before, only now it's running in the UI thread.
> 6. The event handler checks, by means of this.InvokeRequired, to see
> whether it's in the worker thread or the UI thread.
> 7. It will discover that it's in the UI thread, so it will get the string
> out of the StringEventArgs object and put it in the text box's .Text
> field.
>
> One other note: in a simple application, it's fine to put all of that code
> inside the form itself. You'll have no problems with namespaces, etc. If
> your application is complex enough that you've encapsulated the worker
> thread in its own class (and there are plenty of good reasons to do this
> which I won't go into), then you should put the delegate declaration, the
> EventArgs-derived class, and the event into the worker thread's class, and
> put the event handler in your Form class. Just make sure that the event
> and the EventArgs objects are public so that your Form class can see them.
>
>
> "Bruno Jouhier [MVP]" <bjouhier@club-internet.fr> wrote in message
> news:OdKx7ns$EHA.2180@TK2MSFTNGP12.phx.gbl...[color=green]
>> Jeff,
>>
>> I looked quickly at your example, and I see a potential problem with your
>> "callback" method: this method is called from your thread
>> (ThreadedProcess) and it interacts with the GUI (it appends text to the
>> textBoxControls). You have put a monitor to protect it from multi-thread
>> access.
>>
>> This would work ok if WinForms used a "free threaded" model (like the
>> Java toolkits for example), but unfortunately, WinForms is built on top
>> of COM components and it uses an "apartment threaded" model. So, you are
>> not allowed to directly call methods on WinForms components if they have
>> been created by a different thread than yours (you are not allowed to
>> append text to your textBox controls because these controls belong to a
>> different thread). Instead, you have to use the Control.Invoke method
>> everytime you want to interact with these WinForms components from
>> another thread. Control.Invoke will package the call as a Windows
>> message, it will post the message to the message queue of the thread that
>> created the component, and it will wait for the message to be processed
>> and then return the results to your thread.
>>
>> I see that you have used a timeout in Monitor.Enter. I suspect that this
>> is because you have been experiencing some deadlocks in the code that
>> writes to the textBox controls. These deadlocks are typical of
>> cross-thread calls to WinForm components. If you rewrite your callback
>> method so that it goes through Control.Invoke instead, you will see that
>> you won't need any timeout any more, and that you can get rid of the
>> Monitor.Enter/Exit calls, because Control.Invoke is thread-safe (nothing
>> bad happens if two or more threads call Control.Invoke at the same time).
>>
>> Threading is difficult, but Microsoft makes it a bit harder because you
>> have to deal with the "appartment models" that get inherited from COM.
>>
>> Hope this helps.
>>
>> Bruno
>>
>> "Jeff Louie" <anonymous@devdex.com> a écrit dans le message de news:
>> uQBlrwf$EHA.3376@TK2MSFTNGP12.phx.gbl...[color=darkred]
>>> Bruno... Boy that was an understatement. I have spent a _lot_ of time
>>> getting my ThreadedProcess class to work. I had to add a timer to let
>>> the threads complete their task after the console application exited and
>>> added a delegate to notify the caller when the the ThreadedProcess had
>>> closed. I then had to use a thread in the GUI to launch the
>>> ThreadedProcess. If you are up to looking at my code, I updated the page
>>> at:
>>>
>>>
http://www.geocities.com/jeff_louie/call_console.htm
>>>
>>> Regards,
>>> Jeff
>>>>So, threads an not "experimental" any more, they are starting to be
>>> "mainstream". But they are still tricky.<
>>>
>>> *** Sent via Developersdex
http://www.developersdex.com ***
>>> Don't just participate in USENET...get rewarded for it![/color]
>>
>>[/color]
>
>[/color]