Thread View: borland.public.delphi.nativeapi.win32
19 messages
19 total messages
Started by Enquiring Mind
Sat, 12 Jul 2008 17:02
How to dispatch an event handler in a thread-safe way
Author: Enquiring Mind
Date: Sat, 12 Jul 2008 17:02
Date: Sat, 12 Jul 2008 17:02
39 lines
2900 bytes
2900 bytes
Hi, I have an object class which is designed to be used concurrently by multiple threads. It owns a critical section which threads can use to acquire exclusive access to the object before calling one of the object's methods. The class also exposes an event intended to be used to hold an event handler that updates the user interface. The problem is how to dispatch the event handler in a thread-safe way. The obvious solution is to call the TThread class method Synchronize, but because my class is not a thread class descendent, it does not have a thread object that it can pass to the Synchronize method. I need to somehow retrieve the current thread in which my event handler dispatcher is running, so that it can be passed to the Synchronize method. I have found the procedure GetCurrentThreadID, but this returns the ID of the current thread. Is there a way of converting a thread handle or ID to a thread object? (I doubt it, but I ask the question anyway). Or is there a Synchronize method equivalent that can be passed a thread handle or ID in lieu of a TThread object? I can see that an alternative approach could be not to attempt to call the event dispatcher from the thread that it happens to be running in, but to post a message to the application window to ensure that the event handler is executed in the main thread. I note in passing that it would be very nice if a critical section object were to expose the threads that are waiting on it. An elegant solution could then have been to retrieve the current thread from the critical section, assuming that the current thread is the thread which currently has ownership of the critical section. I would have thought that under the hood a critical section must contain a queue of waiting threads. But unfortunately the Delphi TCriticalSection exposes none of this. Any suggestions about how to dispatch the event handler in a thread-safe manner? TIA, EM
Re: How to dispatch an event handler in a thread-safe way
Author: Peter Below (TeamB)
Date: Sun, 13 Jul 2008 08:18
Date: Sun, 13 Jul 2008 08:18
36 lines
2274 bytes
2274 bytes
Enquiring Mind wrote:
> Hi,
>
> I have an object class which is designed to be used concurrently by
> multiple threads. It owns a critical section which threads can use to
> acquire exclusive access to the object before calling one of the
> object's methods. The class also exposes an event intended to be used
> to hold an event handler that updates the user interface.
>
> The problem is how to dispatch the event handler in a thread-safe
> way. The obvious solution is to call the TThread class method
> Synchronize, but because my class is not a thread class descendent,
> it does not have a thread object that it can pass to the Synchronize
> method.
The TThread class has an overload for Synchronize that is a class
method, it does not actually require an instance of the tthread class.
You just call it as
TThread.Synchronize(nil, yourMethod);
Another way to do this would be to post a message to a receiver window
created in the main thread (preferrably via AllocateHwnd, since form
handles can change during the lifetime of an application). This gives
you an asynchronous notification, use SendMessage if you need a
synchronous one.
--
Peter Below (TeamB)
Don't be a vampire (http://slash7.com/pages/vampires),
use the newsgroup archives :
http://www.tamaracka.com/search.htm
http://groups.google.com
Re: How to dispatch an event handler in a thread-safe way
Author: Enquiring Mind
Date: Mon, 14 Jul 2008 14:56
Date: Mon, 14 Jul 2008 14:56
47 lines
3546 bytes
3546 bytes
"Peter Below (TeamB)" <none@nomail.please> wrote in message news:xn0fsmmb31ckmo001@newsgroups.borland.com... > > The TThread class has an overload for Synchronize that is a class > method, it does not actually require an instance of the tthread class. > You just call it as > > TThread.Synchronize(nil, yourMethod); > Many thanks. When I first tried this approach, I thought it didn't work, and I concluded (probably incorrectly) that the first parameter, which is a thread object reference, must match the thread from which the Synchronize method is called so that Synchronize knows which thread to return to once the method to be synchronized has been completed in the main thread. However since then I have tried the same approach in a different context, where it seems to work. So there may have been something else in the first context that caused the thread to be blocked when the Synchronize method was called with nil as the thread reference. Can you confirm that when the thread parameter is set to nil as in your example, Synchronize uses and returns to the thread that it happens to be called from? > Another way to do this would be to post a message to a receiver window > created in the main thread (preferrably via AllocateHwnd, since form > handles can change during the lifetime of an application). This gives > you an asynchronous notification, use SendMessage if you need a > synchronous one. > When I couldn't get the first approach to work in the first context, I switched to this message-based approach, and so far it has worked fine. However, not being familiar with the pitfalls of operating directly with window handles, I just used Application.MainForm.Handle for the destination window handle, and am considering changing this to the actual control that is to be updated, i.e. FControl.Handle, where FControl is a local field of the thread-safe object that holds a reference to the control where messages are to be sent. Are you saying that the handles I have used and am considering using could change during the lifetime of the application? I saw in some previous thread a discussion about the relative merits of a message-based approach. It seemed to suggest that when using the PostMesage function to send a message to a destination control, there is the possibility that the message will be lost along the way. Is that correct, and does it matter? Regards, EM
Re: How to dispatch an event handler in a thread-safe way
Author: Martin James
Date: Mon, 14 Jul 2008 15:40
Date: Mon, 14 Jul 2008 15:40
36 lines
2871 bytes
2871 bytes
> > I saw in some previous thread a discussion about the relative merits of a > message-based approach. It seemed to suggest that when using the > PostMesage function to send a message to a destination control, there is > the possibility that the message will be lost along the way. Is that > correct, and does it matter? No, it is not. Windows does not lose messages. Windows Message Queues are the main method for inter-thread comms in Windows. Losing messages would put Windows into the 'BSOD/crash even before the desktop appears' category. A PostMessage API call will fail of the handle to which is is posted is invalid or if there are more than <a huge number, like 10000> of messages are queued up. Posting to a VCL form handle introduces the remote possibility that the handle may become invalid during the message transit because the target window has its handle recreated. Even thsi remote possiblity can be avoided by posting to a window whose handle will not change because it has only one winproc handler whose only action is to 'forward' the received messages using the TForm.Perform() method. Such a window can be created/obtained by means of the AllocateHwnd Delphi method, as suggested by Peter. or the RegisterWindowClass/CreateWindow APIs. Messages to such a window could contain the target form *instance reference* as one parameter, (so that the winproc knows which form to call 'perform' on), and some other object/whatever as the other. Windows messages do not get lost. PostMessage has not changed since Windows 3.1 and cannot be allowed to fail or change, else 99% of all household apps/services/COM/whatever will break completely and immediately upon loading. PostMessage works from any thread without any reference to the sending thread required. Dump 'synchronize' now - you know it makes sense <g> Rgds, Martin
Re: How to dispatch an event handler in a thread-safe way
Author: Peter Below (TeamB)
Date: Mon, 14 Jul 2008 17:48
Date: Mon, 14 Jul 2008 17:48
41 lines
2542 bytes
2542 bytes
Enquiring Mind wrote: > Can you confirm that when the thread parameter is set to nil as in > your example, Synchronize uses and returns to the thread that it > happens to be called from? Look at the source and your questions will be answered <g>. > When I couldn't get the first approach to work in the first context, > I switched to this message-based approach, and so far it has worked > fine. However, not being familiar with the pitfalls of operating directly > with window handles, I just used Application.MainForm.Handle for the > destination window handle, and am considering changing this to the > actual control that is to be updated, i.e. FControl.Handle, where FControl > is a local field of the thread-safe object that holds a reference to the > control where messages are to be sent. Not safe at all. If the control dies on the thread the very attempt to access the Handle field to send a message to it will cause an access violation. > Are you saying that the handles I have used and am considering using could > change during the lifetime of the application? They can change. Whether they actually do depends on whether you change certain form properties in code during the lifetime of the form. Some property changes (to Border, Bordericon, for example) cause the VCL to destroy and recreate the forms window handle, and with it all handles for the controls. -- Peter Below (TeamB) Don't be a vampire (http://slash7.com/pages/vampires), use the newsgroup archives : http://www.tamaracka.com/search.htm http://groups.google.com
Re: How to dispatch an event handler in a thread-safe way
Author: Peter Below (TeamB)
Date: Mon, 14 Jul 2008 17:51
Date: Mon, 14 Jul 2008 17:51
23 lines
1982 bytes
1982 bytes
Enquiring Mind wrote: > I saw in some previous thread a discussion about the relative merits > of a message-based approach. It seemed to suggest that when using > the PostMesage function to send a message to a destination control, > there is the possibility that the message will be lost along the way. > Is that correct, and does it matter? Oh, I forgot to answer that paragraph: PostMessage can loose a message if the target threads message queue is full (a very unlikely event) or the handle used is invalid. You can check the functions return code to see whether it succeeded. There is also a theorical way to loose a message *after* it was successfully posted: that happens if the target window or the threads message queue is destroyed before the message is processed. Typically that will only happen when the application closes anyway. -- Peter Below (TeamB) Don't be a vampire (http://slash7.com/pages/vampires), use the newsgroup archives : http://www.tamaracka.com/search.htm http://groups.google.com
Re: How to dispatch an event handler in a thread-safe way
Author: Marc Rohloff [TeamB]
Date: Mon, 14 Jul 2008 18:31
Date: Mon, 14 Jul 2008 18:31
16 lines
1875 bytes
1875 bytes
On 14 Jul 2008 10:48:35 -0700, Peter Below (TeamB) wrote: > They can change. Whether they actually do depends on whether you change > certain form properties in code during the lifetime of the form. Some > property changes (to Border, Bordericon, for example) cause the VCL to > destroy and recreate the forms window handle, and with it all handles > for the controls. With the caveat that the form's handle could land up being created in the secondary thread and not the main thread. FWIW, I have never seen anyone check the return value of PostMessage. -- Marc Rohloff [TeamB] marc -at- marc rohloff -dot- com
Re: How to dispatch an event handler in a thread-safe way
Author: Enquiring Mind
Date: Tue, 15 Jul 2008 10:26
Date: Tue, 15 Jul 2008 10:26
30 lines
2461 bytes
2461 bytes
"Martin James" <nospam@tuthill.com> wrote in message news:487b7361@newsgroups.borland.com... > > Dump 'synchronize' now - you know it makes sense <g> > Thanks for that. Making a thread post a message to a UI component to request the execution of a UI update method does in many cases make sense, but at the same time entails problems of its own. The main problem is that if the UI update method accesses any fields of the thread object that posted the message, this is done concurrently with access to the same fields by the thread. Therefore some form of critical section must be included to protect the thread fields from concurrent access. In the case of Synchronize, this is not *normally* necessary, because while the synchronized UI update method is accessing thread fields in the main thread, the secondary thread is suspended, so cannot access the fields at the same time. In summary, the use of PostMessage obliges one to introduce additional critical section objects to protect thread field accesses, whereas the use of Synchronize normally does not. However, in special cases where other secondary threads may access the fields of the secondary thread that called the Synchronize method, a critical section is required even when Synchronize is used. Regards, EM
Re: How to dispatch an event handler in a thread-safe way
Author: Enquiring Mind
Date: Tue, 15 Jul 2008 10:46
Date: Tue, 15 Jul 2008 10:46
46 lines
3073 bytes
3073 bytes
"Peter Below (TeamB)" <none@nomail.please> wrote in message news:xn0fsofuf2ewxp003@newsgroups.borland.com... >> When I couldn't get the first approach to work in the first context, >> I switched to this message-based approach, and so far it has worked >> fine. However, not being familiar with the pitfalls of operating > directly >> with window handles, I just used Application.MainForm.Handle for the >> destination window handle, and am considering changing this to the >> actual control that is to be updated, i.e. FControl.Handle, where > FControl >> is a local field of the thread-safe object that holds a reference to > the >> control where messages are to be sent. > > Not safe at all. If the control dies on the thread the very attempt to > access the Handle field to send a message to it will cause an access > violation. > Is the control that dies destroyed by the application code, or by the OS? Clearly if the application code destroys the control, it can and must assign nil to the thread's reference to the control at the same time, in order to ensure that the state of the thread object is kept fully synchronized with that of the control with which it interacts. >> Are you saying that the handles I have used and am considering using > could >> change during the lifetime of the application? > > They can change. Whether they actually do depends on whether you change > certain form properties in code during the lifetime of the form. Some > property changes (to Border, Bordericon, for example) cause the VCL to > destroy and recreate the forms window handle, and with it all handles > for the controls. > That's useful to know! But although the handles of the controls may change, presumably the VCL control references don't change. So if a thread stores a reference to the control that is to be sent messages, rather than the control's handle, messages should still reach the control, irrespective of any handle change - or doesn't it work like that? Regards, EM
Re: How to dispatch an event handler in a thread-safe way
Author: Marc Rohloff [TeamB]
Date: Tue, 15 Jul 2008 14:36
Date: Tue, 15 Jul 2008 14:36
25 lines
2477 bytes
2477 bytes
On Tue, 15 Jul 2008 11:46:43 +0100, Enquiring Mind wrote: > That's useful to know! But although the handles of the controls may change, > presumably the VCL control references don't change. So if a thread stores a > reference to the control that is to be sent messages, rather than the > control's handle, messages should still reach the control, irrespective of > any handle change - or doesn't it work like that? No, there are a couple of things that could happen. 1) The thread posts the messages to a handle successfully but, before the message is handled, something causes the form or control to recreate its handle. 2) The form (or control) has no handle. This can happen because it is not yet visible or because something has happened that requires the handle to be recreated (In this case the handle is released immediately and a new handle is only created when it is required) In this case, when the thread request a handle to post messages to, the handle will be created in the context of the thread, which is 'not a good thing'(tm) -- Marc Rohloff [TeamB] marc -at- marc rohloff -dot- com
Re: How to dispatch an event handler in a thread-safe way
Author: Enquiring Mind
Date: Tue, 15 Jul 2008 16:48
Date: Tue, 15 Jul 2008 16:48
35 lines
2695 bytes
2695 bytes
"Marc Rohloff [TeamB]" <marc@nospam.marcrohloff.com> wrote in message
news:72gsio2aix3b.dlg@dlg.marcrohloff.com...
>
> No, there are a couple of things that could happen.
>
> 1) The thread posts the messages to a handle successfully but, before
> the message is handled, something causes the form or control to
> recreate its handle.
>
Doesn't this (infinitesimal) risk apply to any message posted to a control?
Consider the following code:
if (FControl<>nil) and (FControl.Handle>0) and (FOnChangeMessage<>0) then
PostMessage(FControl.Handle, FOnChangeMessage, cardinal(Self), 0);
This should retrieve the handle of the destination control just before
PostMessage is called, so the handle should be completely up-to-date. Since
PostMessage is asynchronous, the message should reach the destination queue
very quickly, hopefully before the destination control's handle has had a
chance to change.
> 2) The form (or control) has no handle. This can happen because it is
> not yet visible or because something has happened that requires the
> handle to be recreated (In this case the handle is released
> immediately and a new handle is only created when it is required)
> In this case, when the thread request a handle to post messages to,
> the handle will be created in the context of the thread, which is 'not
> a good thing'(tm)
>
Can't we can check for this before calling PostMessage, as in the above?
Regards,
EM
Re: How to dispatch an event handler in a thread-safe way
Author: Peter Below (TeamB)
Date: Tue, 15 Jul 2008 17:49
Date: Tue, 15 Jul 2008 17:49
34 lines
2299 bytes
2299 bytes
Enquiring Mind wrote: > > Not safe at all. If the control dies on the thread the very attempt > > to access the Handle field to send a message to it will cause an > > access violation. > > > Is the control that dies destroyed by the application code, or by the > OS? By application code or the VCL, when the form hosting the control is destroyed. > Clearly if the application code destroys the control, it can and > must assign nil to the thread's reference to the control at the same > time, in order to ensure that the state of the thread object is kept > fully synchronized with that of the control with which it interacts. Definitely. One could alternatively make use of the notification mechanism build into the VCL for components (FreeNotification et al.). I would prefer that since it allows the thread object itself to detect when the control reference is about to become invalid. That's allways more dependable than having to remember elsewhere to set the control reference to nil before the control dies. The control reference is unchanged when the handle changes, by the way. -- Peter Below (TeamB) Don't be a vampire (http://slash7.com/pages/vampires), use the newsgroup archives : http://www.tamaracka.com/search.htm http://groups.google.com
Re: How to dispatch an event handler in a thread-safe way
Author: Marc Rohloff [TeamB]
Date: Tue, 15 Jul 2008 18:37
Date: Tue, 15 Jul 2008 18:37
19 lines
2122 bytes
2122 bytes
On Tue, 15 Jul 2008 17:48:55 +0100, Enquiring Mind wrote: > Doesn't this (infinitesimal) risk apply to any message posted to a control? Yes, though I wouldn't call it infinitesimal. I also know all the fun some people have had trying to debug this issue. > if (FControl<>nil) and (FControl.Handle>0) You are aware that calling FControl.Handle will actually create the handle if it hasn't been created yet? You need to call FControl.HandleAllocated. > Can't we can check for this before calling PostMessage, as in the > above? So what are you going to do then? Not send your message. That seems almost as bad as sending it and having it get lost on the way. -- Marc Rohloff [TeamB] marc -at- marc rohloff -dot- com
Re: How to dispatch an event handler in a thread-safe way
Author: Martin James
Date: Wed, 16 Jul 2008 13:19
Date: Wed, 16 Jul 2008 13:19
48 lines
3603 bytes
3603 bytes
> Making a thread post a message to a UI component to request the execution > of a UI update method does in many cases make sense, but at the same time > entails problems of its own. The main problem is that if the UI update > method accesses any fields of the thread object that posted the message, > this is done concurrently with access to the same fields by the thread. > Therefore some form of critical section must be included to protect the > thread fields from concurrent access. In the case of Synchronize, this is > not *normally* necessary, because while the synchronized UI update method > is accessing thread fields in the main thread, the secondary thread is > suspended, so cannot access the fields at the same time. Yes, if you access field of the thread, (ie. don't do that!). > In summary, the use of PostMessage obliges one to introduce additional > critical section objects to protect thread field accesses, whereas the use > of Synchronize normally does not. No. PostMessage has parameters whereas 'TThread.synchronize' does not. If all the data required for the operation is contained in the parameters, then it is not necessary to access any field of the thread & so no extra protection is required. 32-bit parameters can be passed directly in wParam/lParam and, since '32-bit' includes instance reference pointers, entire classe instances like buffers, sockets, subsystems can be passed easily with PostMessage. > However, in special cases where other secondary threads may access the > fields of the secondary thread that called the Synchronize method, a > critical section is required even when Synchronize is used. Yes - as above, *don't access fields of TThread objects from other threads*. This is unnecessary and usually dangerous/unsafe for the reasons you give above and others. What happens if the TThread wants to terminate and free itself? It dare not in case one of the other threads wants to access its fields for some reason. If PM is used to transfer data like integers or instance references *that are not itself or objects that it has it itself owns and must free before exit*, then there is no problem - PM off whatever you like, whenever you like, and die straight after if you want - the PM data willl still be fine. Rgds, Martin Rgds, Martin
Re: How to dispatch an event handler in a thread-safe way
Author: Enquiring Mind
Date: Wed, 16 Jul 2008 14:37
Date: Wed, 16 Jul 2008 14:37
17 lines
1855 bytes
1855 bytes
"Marc Rohloff [TeamB]" <marc@nospam.marcrohloff.com> wrote in message news:lqfthmiyn2qd$.dlg@dlg.marcrohloff.com... > You are aware that calling FControl.Handle will actually create the > handle if it hasn't been created yet? You need to call > FControl.HandleAllocated. > Thanks for pointing that out. >> Can't we can check for this before calling PostMessage, as in the >> above? > So what are you going to do then? Not send your message. That seems > almost as bad as sending it and having it get lost on the way. > At least the application becomes aware of the potential problem before it occurs, and can react accordingly.
Re: How to dispatch an event handler in a thread-safe way
Author: Enquiring Mind
Date: Wed, 16 Jul 2008 15:32
Date: Wed, 16 Jul 2008 15:32
64 lines
4558 bytes
4558 bytes
"Martin James" <nospam@tuthill.com> wrote in message news:487df568$1@newsgroups.borland.com... > > Yes, if you access field of the thread, (ie. don't do that!). > The main reason for calling an event handler may be to display some thread state variables in the user interface, so you have no option but to access the thread's fields either directly or indirectly through a method or global variables. > > No. PostMessage has parameters whereas 'TThread.synchronize' does not. > If 'TThread.synchronize' usually calls an event dispatcher, which assigns 'Self' to the 'Sender' parameter of the TNotifyEvent. In the case of PostMessage, OTOH, the Sender reference has to be put in the WParam parameter, leaving one only one other 32-bit parameter for other data. > all the data required for the operation is contained in the parameters, > then it is not necessary to access any field of the thread & so no extra > protection is required. 32-bit parameters can be passed directly in > wParam/lParam and, since '32-bit' includes instance reference pointers, > entire classe instances like buffers, sockets, subsystems can be passed > easily with PostMessage. If WParam contains a reference to the Thread object that sent the message, then there is nothing to prevent the message handler from accessing the (visible) fields and properties of the thread object. Thus the fields/properties can be accessed concurrently by the message handler and by the thread that sent the message. More generally, if either parameter contains a pointer, there is nothing to prevent concurrent access of the data referenced by the pointer. When pointers cross thread boundaries there are potential problems! > Yes - as above, *don't access fields of TThread objects from other > threads*. This is unnecessary and usually dangerous/unsafe for the reasons > you give above and others. What happens if the TThread wants to terminate > and free itself? It dare not in case one of the other threads wants to > access its fields for some reason. > Presumably any thread wishing to access the fields of another thread could first check that the other thread object is assigned. > If PM is used to transfer data like integers or instance references *that > are not itself or objects that it has it itself owns and must free before > exit*, then there is no problem - PM off whatever you like, whenever you > like, and die straight after if you want - the PM data willl still be > fine. > You seem to be saying that provided the two message parameters are value parameters, or pointers to variables that the sender doesn't own and will never again use, then there's no problem - agreed because value parameters will contain copies of the thread fields, and pointers will only ever be used again by the recipient of the message. In most practical cases you need to transfer more data from the sender to the receiver than will fit into 2 32-bit cardinals - e.g. a string. So whenever this is the case, the sender thread has to create a dynamic variable, assign the data to be transferred to it, pass it by reference to a message parameter, post the message, then for safety immediately set the pointer to nil. The message handler has to dereference the pointer, copy the data into some local variables, and dispose of the variable. So it can be done, but at the cost of considerable extra work!
Re: How to dispatch an event handler in a thread-safe way
Author: Martin James
Date: Thu, 17 Jul 2008 06:53
Date: Thu, 17 Jul 2008 06:53
166 lines
9397 bytes
9397 bytes
"Enquiring Mind" <Enquiring.Mind@nospam.btopenworld.com> wrote in message
news:487e14bb@newsgroups.borland.com...
>
> "Martin James" <nospam@tuthill.com> wrote in message
> news:487df568$1@newsgroups.borland.com...
>>
>> Yes, if you access field of the thread, (ie. don't do that!).
>>
> The main reason for calling an event handler may be to display some thread
> state variables in the user interface, so you have no option but to access
> the thread's fields either directly or indirectly through a method or
> global variables.
Thread state variables? The OS certainly keeps thread state variables, but
I can't see, off-hand, why TThreads should have any state data that is
required outside the TThread. If these state variables are really state
variables of some functionality that is not really part of the thread, then
they should be in a class whose methods/properties are called from <some
thread>.
>> No. PostMessage has parameters whereas 'TThread.synchronize' does not.
>> If
>
> 'TThread.synchronize' usually calls an event dispatcher, which assigns
> 'Self' to the 'Sender' parameter of the TNotifyEvent. In the case of
> PostMessage, OTOH, the Sender reference has to be put in the WParam
> parameter, leaving one only one other 32-bit parameter for other data.
1) Does it have to matter which thread sent the the data? What is 'Sender'
in the case of an inter-thread communication?
2) One 32-bit parameter is all you need anyway. That's enough for a class
reference, which is enough to transfer megabytes of data and hundreds of
inherited methods around your system, should you so chose.
>> all the data required for the operation is contained in the parameters,
>> then it is not necessary to access any field of the thread & so no extra
>> protection is required. 32-bit parameters can be passed directly in
>> wParam/lParam and, since '32-bit' includes instance reference pointers,
>> entire classe instances like buffers, sockets, subsystems can be passed
>> easily with PostMessage.
>
> If WParam contains a reference to the Thread object that sent the message,
> then there is nothing to prevent the message handler from accessing the
> (visible) fields and properties of the thread object. Thus the
> fields/properties can be accessed concurrently by the message handler and
> by the thread that sent the message.
Yes, they can, so don't do it! Think about it - what thread field is really
needed outside the thread?
More generally, if either parameter
> contains a pointer, there is nothing to prevent concurrent access of the
> data referenced by the pointer. When pointers cross thread boundaries
> there are potential problems!
Yes, exactly, so don't do it! Use classes/objects to supply parameters,
perform methods and hold state data.
>> Yes - as above, *don't access fields of TThread objects from other
>> threads*. This is unnecessary and usually dangerous/unsafe for the
>> reasons you give above and others. What happens if the TThread wants to
>> terminate and free itself? It dare not in case one of the other threads
>> wants to access its fields for some reason.
>>
> Presumably any thread wishing to access the fields of another thread could
> first check that the other thread object is assigned.
Nope - won't work:
1) A TThread has two aspects - the Delphi TThread class and the OS thread
object. Just because one exists, there is no guarantee that the other does
too.
2) Even if a check was possible, the check is only valid at the time it was
made. On the next line of code after the 'if assigned()', the thread may
have disappeared.
>> If PM is used to transfer data like integers or instance references *that
>> are not itself or objects that it has it itself owns and must free before
>> exit*, then there is no problem - PM off whatever you like, whenever you
>> like, and die straight after if you want - the PM data willl still be
>> fine.
>>
> You seem to be saying that provided the two message parameters are value
> parameters, or pointers to variables that the sender doesn't own and will
> never again use, then there's no problem - agreed because value parameters
> will contain copies of the thread fields, and pointers will only ever be
> used again by the recipient of the message. In most practical cases you
> need to transfer more data from the sender to the receiver than will fit
> into 2 32-bit cardinals - e.g. a string. So whenever this is the case, the
> sender thread has to create a dynamic variable, assign the data to be
> transferred to it, pass it by reference to a message parameter, post the
> message, then for safety immediately set the pointer to nil.
That is one way. You seem to be fixated on using thread fields to transfer
data in/out of secondary threads. I think you've been looking at the
Borland examples and 'How To Use TThread' pages on the net. Try not to do
that! <g>
The message handler has to
> dereference the pointer, copy the data into some local variables, and
> dispose of the variable.
That is certainly one way.
> So it can be done, but at the cost of considerable extra work!
Yes, but then again. often the 'considerable extra work' has already been
done. If the state data is not in thread fields, (where it should not be
anyway!), but in a class instance that was supplied/queue/whatever to the
thread, then thread fields do not have to be copied inhto/outof anywhere
because they do not exist. Let me try an example - a web crawler, say.
There is probably already a class to represent a web page - it just needs
adding to so that it can hold results/extra methods as well, eg:
TwebPage=class
thisPage:string;
linksOnThisPage:TstringList;
public
URI:string;
getThisPage(usingThisComponent:TidHTTP);
parsePageForLinks();
errorMessage:string;
end;
Note:I haven't bothered with constructors etc.
The main thread could contruct one of these and use a 'plonked on form'
TidHTTP instance to call getThispage and then parsePageForLinks. That
should work OK, but the main thread would freeze and so, instead, you queue
off this instance to a thread/threads that have their own TidHTTP and let
the thread call the methods and PostMessage the TwebPage back. You could
then display the links in a memo in the message-handler. If there was some
exception, you can return the error E.message in the field provided. After
displaying the links, the main thread could then construct as many new
TwebPage instances as there are links and queue them off to the thread/s to
follow the links - either that or, better, have the secondary thread/s
themselves create/issue the newTwebPage instances to themselves.
No TThread fields required outside the TThread. No main-thread freeze, no
main-thread involvement at all while the page/s are being retrieved. The
main thread does not know, or care, what TThread/TidHTTP did the work, only
that the work got done. This is important in apps like this because any one
of a pool of threads could have done the work and, more importantly, that
thread is probably already doing more work on other TwebPage instances when
its earlier efforts are being displayed in a main-thread memo.
Any attempt to use non-locked TThread fields to hold results for display
would be disastrous in such an app. Using TThread.synchronize to hold up
the TThreads until the results could be displayed would tie up the threads
to such an extent that performance would be crippled: threads that could be
calling TidHTTP get/post are sitting around waiting for their synchronized
methods to get handled. That is bad on a single processor system, worse on
multiple cores since all the actions are held up on the one processor
running the main thread.
Give up on all those examples of using TThread that insist on using TThread
fields and TThread.synchronize. I know that Borland, and most 'How to use
TThread' examples on the net, show TThread.synchronize and TThread field use
from outside the thread. They all suck.
Rgds,
Martin
Re: How to dispatch an event handler in a thread-safe way
Author: Enquiring Mind
Date: Thu, 17 Jul 2008 15:04
Date: Thu, 17 Jul 2008 15:04
260 lines
11684 bytes
11684 bytes
"Martin James" <nospam@tuthill.com> wrote in message
news:487eec50@newsgroups.borland.com...
> Thread state variables? The OS certainly keeps thread state variables,
> but I can't see, off-hand, why TThreads should have any state data that is
> required outside the TThread. If these state variables are really state
> variables of some functionality that is not really part of the thread,
> then they should be in a class whose methods/properties are called from
> <some thread>.
>
The term 'thread state variable' that I used may be too general a term. A
better term would have been 'thread variable'. By this I meant any field
that is a member of a thread object that is accessible to other threads
either because it's public, or because it can be accessed through a
property. Data normally exclusive to a thread may be held in one of 3
locations as far as I can see: a) in fields of the thread object; b) in
local variables of the Execute method or of any method or procedure directly
or indirectly called by it; c) in a threadvar variable. (Perhaps you might
like to elucidate the relative pros and cons of these 3 types of thread
storage).
Having defined what I mean by a thread state variable, then it's a matter
for a particular application whether or not a thread variable always remains
for ever private to the thread, or whether there is a need for it to be
communicated to another thread, so that, for example, it can be displayed in
the user interface.
>>
>> 'TThread.synchronize' usually calls an event dispatcher, which assigns
>> 'Self' to the 'Sender' parameter of the TNotifyEvent. In the case of
>> PostMessage, OTOH, the Sender reference has to be put in the WParam
>> parameter, leaving one only one other 32-bit parameter for other data.
>
> 1) Does it have to matter which thread sent the the data? What is
> 'Sender' in the case of an inter-thread communication?
>
It matters if the main thread needs to access the thread's fields or
properties in an event handler declared in say the main form and assigned to
an event exposed by a particular TTthread descendent class. Howveer, this is
only one of several methods for transferring data between threads!
> 2) One 32-bit parameter is all you need anyway. That's enough for a class
> reference, which is enough to transfer megabytes of data and hundreds of
> inherited methods around your system, should you so chose.
>
Agreed.
>> If WParam contains a reference to the Thread object that sent the
>> message, then there is nothing to prevent the message handler from
>> accessing the (visible) fields and properties of the thread object. Thus
>> the fields/properties can be accessed concurrently by the message handler
>> and by the thread that sent the message.
>
> Yes, they can, so don't do it! Think about it - what thread field is
> really needed outside the thread?
>
I agree it's desirable to avoid this, but the concurrent access issue can be
controlled by various means, like Synchronize, TCriticalSection objects,
etc.
> More generally, if either parameter
>> contains a pointer, there is nothing to prevent concurrent access of the
>> data referenced by the pointer. When pointers cross thread boundaries
>> there are potential problems!
>
> Yes, exactly, so don't do it! Use classes/objects to supply parameters,
> perform methods and hold state data.
>
>> Presumably any thread wishing to access the fields of another thread
>> could first check that the other thread object is assigned.
>
> Nope - won't work:
>
> 1) A TThread has two aspects - the Delphi TThread class and the OS thread
> object. Just because one exists, there is no guarantee that the other
> does too.
>
> 2) Even if a check was possible, the check is only valid at the time it
> was made. On the next line of code after the 'if assigned()', the thread
> may have disappeared.
Good point. However thread A can request the suspension of thread B, and
when suspended access some of its properties, and finally resume thread B.
>
> That is one way. You seem to be fixated on using thread fields to
> transfer data in/out of secondary threads. I think you've been looking at
> the Borland examples and 'How To Use TThread' pages on the net. Try not
> to do that! <g>
>
> The message handler has to
>> dereference the pointer, copy the data into some local variables, and
>> dispose of the variable.
>
> That is certainly one way.
>
After further thought I have concluded that the way I described is the
safest way of transferring data when using *messages* to communicate between
threads (that is, without resorting to a critical section, which would
defeat the object of using PostMessage instead of Synchronize). The sender,
say thread A, creates a container object and copies thread state data to the
container object. It then sends the recipient, say thread B, a message,
including in it a reference to the container object, and from this point in
time on thread A *never* again accesses the container object. Thread B in
the message handler copies data from the container, processes tha data as
necessary, then destroys the container object. Since the data in the
container object can *never* be accessed concurrently by more than one
thread, this approach must be as safe as houses.
However when you are using Synchronize there is an even better way of
transferring state data in such a way as to preclude the possibility of
concurrent access - in 'by-value' parameters of the event handler. These
exist for only as long as the event handler exists on the stack. The most
general by-value paramter could be a byte array containing the data to be
transferred in the form of a binary stream.
>
> Yes, but then again. often the 'considerable extra work' has already been
> done. If the state data is not in thread fields, (where it should not be
> anyway!), but in a class instance that was supplied/queue/whatever to the
> thread, then thread fields do not have to be copied inhto/outof anywhere
> because they do not exist.
If a thread holds data, which under normal use is exclusive to itself, in an
object, then this is just an extension of a thread field. The thread still
needs a field containing a reference to the said object, and the thread
should usually own the object. The thread cannot just pass a reference to
this object to another thread an expect concurrent acces to be prevented.
Even if the object comes from a queue, the queue itself will be shared by
multiple threads.
>Let me try an example - a web crawler, say.
Many thanks for the example - I will study it with interest. But I don't
know enough about web programming to make any meaningful comments on it at
the moment!
I have an example of the approach I have been describing. It uses the
dreaded TTcpServer component. I had to use messages to transmit data between
the OnAccept method handler (that runs in a client socket thread) and the
main form, because without deriving a new thread class from
TClientSocketThread defined in the VCL, I cannot add events or event
disptachers to the thread class. The (tested) code is below:
const
WM_ClientSocketThread_Cycle= WM_APP + 3;
type
{The following class encapsulates data to be transmitted from client
socket thread to main thread}
TCycleState= class
MessageCount: integer;
LastMessage: string;
ThreadID: integer;
end;
TForm1 = class(TForm)
TcpServer1: TTcpServer;
{...}
procedure TcpServer1Accept(Sender: TObject;
ClientSocket: TCustomIpClient);
{...}
private
{...}
{Thread event handlers:}
procedure ThreadSocketEchoOnCycle(MessageCount: integer; LastMessage:
string;
ThreadId: integer);
protected
procedure WMClientSocketThreadCycle(var Msg: TMessage);
message WM_ClientSocketThread_Cycle;
public
{ Public declarations }
end;
implementation
procedure TForm1.TcpServer1Accept(Sender: TObject;
ClientSocket: TCustomIpClient);
{Important note: This event handler runs inside the client socket thread
created
by the TcpServer component.}
var
CurrClientSocketThread: TClientSocketThread;
{Thread created for current connection, under which this method is
running}
procedure DoListen;
{This option listens for messages on a single connection - for
illustrative
purposes only}
var
MessageCount: integer;
LastMessage: string;
Terminated1: Boolean;
Response: string;
CycleState: TCycleState;
begin
Terminated1:= False;
MessageCount:= 0;
repeat
{Receive next message on socket:}
LastMessage:= ClientSocket.Receiveln;
Inc(MessageCount);
{Notify cycle to enable UI to be updated:}
CycleState:= TCycleState.Create; {Create container object}
CycleState.LastMessage:= LastMessage;
CycleState.MessageCount:= MessageCount;
CycleState.ThreadID:= CurrClientSocketThread.ThreadID;
PostMessage(Form1.Handle, WM_ClientSocketThread_Cycle,
Cardinal(CycleState), 0);
CycleState:= nil; {Not needed, but included to emphasize the point!}
{Determine response:}
if (LastMessage= 'quit') or (LastMessage= '') then
begin
Terminated1:= True;
Response:= 'Terminated';
end
else
Response:= 'Echo: ' + LastMessage;
{Send response:}
ClientSocket.SendLn(Response);
until Terminated1;
end;
begin
{Retrieve current client socket thread}
CurrClientSocketThread:= ClientSocket.GetThreadObject;
try
{Notify client that connection has been made:}
ClientSocket.SendLn('You are connected');
{Perform thread cycles}
DoListen;
finally
{Clean up...}
end;
end;
procedure TForm1.WMClientSocketThreadCycle(var Msg: TMessage);
{Message handler}
var
CycleState: TCycleState;
begin
{Retrieve reference to container object from message:}
CycleState:= TObject(Msg.WParam) as TCycleState;
{Process data transferred in an event handler:}
with CycleState do
ThreadSocketEchoOnCycle({.}MessageCount, {.}LastMessage, {.}ThreadID);
{Destroy container:}
CycleState.Free;
end;
Any comments?
Regards,
EM
Re: How to dispatch an event handler in a thread-safe way
Author: Enquiring Mind
Date: Fri, 18 Jul 2008 11:03
Date: Fri, 18 Jul 2008 11:03
139 lines
7077 bytes
7077 bytes
"Martin James" <nospam@tuthill.com> wrote in message
news:487eec50@newsgroups.borland.com...
>
> Let me try an example - a web crawler, say. There is probably already a
> class to represent a web page - it just needs adding to so that it can
> hold results/extra methods as well, eg:
>
> TwebPage=class
> thisPage:string;
> linksOnThisPage:TstringList;
> public
> URI:string;
> getThisPage(usingThisComponent:TidHTTP);
> parsePageForLinks();
> errorMessage:string;
> end;
>
> Note:I haven't bothered with constructors etc.
>
> The main thread could contruct one of these and use a 'plonked on form'
> TidHTTP instance to call getThispage and then parsePageForLinks. That
> should work OK, but the main thread would freeze and so, instead, you
> queue off this instance to a thread/threads that have their own TidHTTP
> and let the thread call the methods and PostMessage the TwebPage back.
> You could then display the links in a memo in the message-handler. If
> there was some exception, you can return the error E.message in the field
> provided. After displaying the links, the main thread could then
> construct as many new TwebPage instances as there are links and queue them
> off to the thread/s to follow the links - either that or, better, have
> the secondary thread/s themselves create/issue the newTwebPage instances
> to themselves.
>
Now that I've had time to partially digest your example, I would make the
following comments. Both the main thread and the thread created to fetch the
web page in the background have concurrent access to the same TWebPage
object. Therefore the fields of this object are not *inherently*
thread-safe, because while a secondary thread is downloading the web page
into the ThisPage field, the main thread could be trying to access the
ThisPage field, for example to repaint the user interface. Of course, this
can be avoided by good programming, but it would be nice if the program
structure could exclude the possibility at source!
In essence, the reason for using a thread is to execute a single lengthy
procedure in the background, and when it terminates, to communicate the
results of the lengthy procedure to the main thread, so that they can be
displayed in the user interface. With only a marginal loss of efficiency
this could be more simply accomplished by communicating the results in an
OnTerminate event handler assigned to the thread. It's true that this is
synchronized, but it only happens once in the life of the thread - just
before it is destroyed - so the loss in efficiency seems marginal. Moreover,
the execution of the OnTerminate handler is fast compared to the execution
of the Execute method of the thread, because it just copies the data from a
couple of secondary thread variables to main thread variables. I am not sure
whether the secondary thread dispatches the OnTerminate event handler
synchronously or asynchronously, but because of its speed, it shouldn't
matter too much even if the call is synchronous. There should be no great
loss in efficiency even when multiple threads try to dispatch the
OnTerminate event handler at precisely the same time. The OnTerminate event
handler can copy the data in thread fields to user interface variables
without using a critical section in the certainty that the thread won't be
accessing the fields at the same time - after all the thread procedure has
terminated. For better performance, one can by all means use the PostMessage
approach, and post the message when the thread terminates.
The pseudo-code for the alternative approach (without using a thread pool,
and omitting all error handling) could be:
TThreadFetchWebpage= class(TThread)
private
FURI: string;
FWebpage: string; {Owned by thread}
FLinksOnPage: TStringList; {Owned by thread}
protected
procedure Execute; override;
public
constructor Create(CreateSuspended: boolean; URI: string);
destructor Destroy; override; {Frees stringlist etc}
procedure GetOutputData(var Webpage: string; LinksOnPage: TStringList);
end;
procedure TThreadFetchWebpage.Execute;
var
idHTTP: TidHTTP;
begin
idHTTP:= TidHTTP.Create(nil);
try
{Code to download web page into FWebpage using idHTTP component:}
..
{Code to extract links from FWebpage into FLinksOnPage:}
...
finally
idHTTP.Free;
end;
end;
procedure TThreadFetchWebpage.GetOutputData(var Webpage: string;
LinksOnPage: TStringList);
begin
{*Copy* data, not references:}
Webpage:= Copy(FWebpage, ...);
LinksOnPage.Text:= FLinksOnPage.Text;
end;
procedure TForm1.ThreadFetchWebpageTerminate(Sender: TObject);
var
ThreadFetchWebpage: TThreadFetchWebpage;
begin
ThreadFetchWebpage:= Sender as TThreadFetchWebpage;
{Copy results from thread into form fields:}
ThreadFetchWebpage.GetOutputdata(Self.FWebpage, Self.FLinksOnPage);
{Destroy thread}
ThreadFetchWebpage.Free;
{Update user interface}
DataToControls;
Invalidate;
end;
procedure TForm1.ButtonFetchWebpageClick(Sender: TObject);
var
URI: string;
ThreadFetchWebpage: TThreadFetchWebpage;
begin
URI:= EditURI.Text;
{Create thread to fetch web page:}
ThreadFetchWebpage:= TThreadFetchWebpage.Create(True, URI);
ThreadFetchWebpage.OnTerminate:= ThreadFetchWebpageTerminate;
{Start thread:}
ThreadFetchWebpage.Resume;
end;
This assumes that if the user clicks the fetch button several times in rapid
succession, the form only displays the last web page to reach it. An
alternative approach would be to create a new tabsheet for each web page
fetched.
Regards,
EM
Thread Navigation
This is a paginated view of messages in the thread with full content displayed inline.
Messages are displayed in chronological order, with the original post highlighted in green.
Use pagination controls to navigate through all messages in large threads.
Back to All Threads