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
> 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
> 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
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
> 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.
> 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.
> 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
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
>> 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
> 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)
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
> > 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.
> 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.
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
> 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.
'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!
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
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
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