Re: Disconnect TIdHttp in thread

Giganews Newsgroups
Subject: Re: Disconnect TIdHttp in thread
Posted by:  Martin James (mjames_falc…@dial.pipex.com)
Date: Wed, 27 Jul 2005

See 6 :)

'Ways to get into avoidable trouble with threads, V1.2'

1) Continually creating and destroying threads that do the same job.  Much
better to create the thread/s once only, at app startup, and have them loop
around some blocking communication mechanism, waiting for stuff to do.  At
the cost of some extra, avoidable memory use, this:

a) Eliminates the overhead of continually creating and destroying threads.
b) Moves thread termination issues to app close time, when it does not
matter too much.
c) Eases any problems with your own object-lifetime-management and also puts
leaky 3rd-party components into the 'don't care' category - if some
object/component is created at startup and freed at shutdown, who cares if
it leaks 256 bytes on free?  (Note, TidHTTP does not leak, but other
stuff?).

2) Waiting in an event-handler for thread results with 'TThread.waitFor', or
some other hard-wait mechanism.  This is philosophically silly - if thread
results have to be waited for, what's the point of using a thread?  Also,
such waits are unfriendly to the point of disaster with other non-queued
comms mechansims like the dreaded 'synchronize', leading to increased
possibility of deadlock.  IMHO, 'TThread.waitFor' should be removed from the
class as too dangerous/silly for use.

3) Waiting in an event-handler for thread results with
'sleep-application.processMessages' loops.  Instead of being just silly and
dangerous, this is wasteful and slow as well.  The longer the sleep, the
longer the average latency before it is noticed that the thread has
finished.  The shorter the sleep, the more CPU is wasted on A.P calls and
pointless context-changes.  This is apart from the 'reentrancy' problems
with application.processMessages, which usually results in peppering your
code with 'busy' flags.  Don't wait!  Signal your thread to do something
*and exit the event handler*.  When the thread is done, it can send the
results using a custom message and 'postMessage' to fire a message-handler,
so firing *a seperate event handler*.  This may require a simple, (or
complex:) state-machine, where the signal/request to the thread has to carry
some state data as well as any other info/parameters, and return this state
in the results.  This might seem awkward, and it is, but it's the only way
to get the best from multiThreading in a safe, expandable, reliable way.

4) Using hard-lock communication/signalling mechanisms, like
'TThread.synchronize'.  Generates a guaranteed two context-changes per call
and blocks the calling thread until the synchronized method has been
executed by the main thread.  Main advantage - allows 'access' to
non-thread-safe VCL components from secondary threads. Actually, of course,
it does not - it allows the main thread the ability to call a method of a
secondary thread while the secondary thread is guaranteed to be not running
& so VCL component properties/methods can be safely read/written/called with
data from the seconday thread without interference from the secondary thread
execution.  Apart from the aforementioned inefficiency, this is fine as long
as the main thread is actually available.  If it's busy, or worse, stuck on
some other hard-lock comms mechanism, then you're either wasting time or
deadlocked.  As a multiThreaded app gets more complex, the possibilities for
deadlock when using hard-wait comms increases super-proportionally, probably
geometrically.  An app rapidly becomes unreliable, undebuggable and
unuseable.  Dump 'synchronize' and use postMessage for comms with the main
thread.  Use postThreadMessage or some producer-consumer queue to queue
request/data objects to threads or to queue result objects to other
secondary threads.  Every multitasking OS ever developed has
primitives/mechansims for queued inter-thread comms - Windows is no
exception.  There*is* a reason for this :)

5) Failure to appreciate OS functionality and available APIs.  Events,
mutexes and semaphores are available on 99% of all household OS and have
their uses.  Developers of multiThreaded apps should be aware of them.
Typical 'failure to appreciate' - secondary threads polling a TThreadList
for work, often with a poor, latency-ridden, cycle-wasting 'sleep(1)' loop
or worse - a low-priority-thread-killing, processor-eating 'sleep(0)' loop.
Use events or semaphores to make an efficient producer-consumer queue.

6) Using components owned by forms in a thread.  Developers do this because
it's easy.  Plonk a component on the form, make some event handler methods
with the object-inspector, use 'synchronize' to make the VCL calls
thread-safe, and create some thread to run the component.  This is
easier/quicker than the alternative of creating the component in the thread
and attaching event-handlers in code.  Unfortunately, this makes the
operation of the thread dependent on the existence of the form and the
single component on it.  If the form is freed before the thread is
terminated then the thread will AV.  This often happens when trying to close
down an app - Delphi closes the form before the OS can terminate the thread.
This means adding a communication mechanism to the thread, to ensure that it
has terminated, before the form can close.  Often, such a mechansim has to
be available anyway - perhaps closing forms and terminating the bound thread
is part of the normal functionality of the app - if so, fine.  If the form
and thread are around for the life of the app, however, using form-owned
components in threads forces a thread-shutdown mechanism upon the developer
whether there is some other reason for it or not.  Another snag with
'form-owned' components is the difficulty of creating multiple threads
because the thread is not encapsulated.  Creating two threads that use the
same component is a disaster.  Dropping multiple components onto the form
and passing each to a thread is possible but inflexible and messy.  Creating
the component in the thread reduces the task of creating multiple threads to
a for/next loop round the TThread.create call.  More threads can be easily
created and the number of threads is easily reduced at runtime, allowing
easy 'tuning'.  Actually, it's quite easy to create the event-handler
skeletons.  Temporarily plonk a component onto the form and use the object
inspector to create all the empty event handlers.  Copy and paste the entire
block of event-handlers to create a second set at the end of your unit. Use
find/replace on this second set to change the class type from 'TForm1', (or
whatever), to 'TmyThread', (or whatever). Copy-and-paste the method
declarations from the form class defn. to the thread class defn.  Compile,
and Delphi will remove all the Tform event-handlers because they are empty.
Delete the 'temporary' component from the form.  The TThread class now has
event handlers with all the correct profiles, ready to be loaded into the
event properties of a component created in the thread.

7) Explicitly terminating, and waiting for successful termination, of
threads that do not need to be terminated or waited for, ie.
'freeOnTerminate:=false'.  This, I'm afraid, is all too common.  There is
no need to involve any other threads in the freeing of TThreads.  They are
quite happy to free themselves on exit.  If a thread gets stuck on a
blocking call, there's no point in setting 'terminate' and waiting for the
thread to finish, eg. buy calling 'TThread.waitFor' or using some horrible
loop.  Doing so results in the 'Cannot shut down my server' type of posts,
where threaded subsystems just get stuck when trying to terminate threads
that do not want to be terminated.  Try to design your threads so that it
just does not matter if the odd one gets stuck for 2 minutes on some NETBIOS
call. Do whatever is needed to signal the thread to terminate itself when
it's done and then *just forget about it*.  If you need another thread of
this class, create another one.  The 'orphaned' thread will eventually time
out or get an exception, wake up, realise it has to die and exit, freeing
itself.  Either that, or it will still be lying around when the app closes
and Windows eats it up.

8) Using an 'onTerminate' handler.  Uses 'synchronize' - dangerous waste of
time.  Just do not use it.  If the main thread *really has* to know that a
thread has terminated, have the thread  *postMessage* off a last gasp
message before exiting.  Note the asterisks - 'queued, does not block the
caller'.

9) Expecting complex, threaded subsystems to simply shut down immediately
when freed.  Cannot be done in general.  Components that proffer to support
this kind of functionality are in trouble.  A typical example would be a
complex TCP server with dozens of threads and connected clients. It is not
reasonable, or safe, to simple call 'MyServer.free' and expect an immediate
return with all clients disconnected and all threads closed.  Any component
that attempts this is likely to end up siezing the app because of failed
thread termination and/or deadlocks, especially if the 'free' is attempted
from the main thread.  It *is* reasonable to provide a 'shutdown' method
that takes a 'TNotifyEvent' callback, returns 'immediately' and later fires
the callback with 'shutdownComplete' status or some error message. Again,
'queued, does not block the caller'.

10) Use VB first.  Until recently, VB developers were denied easy access to
multiThreading.  As a result, the 'DoEvents' generation of Windows
programmers was born.

11) Believe all the 'ThreadFunk' created by developers who once tried a
multiThreaded app & couldn't get it to work reliably.  See 1-10, or the
Borland thread examples.  There are too many developers who are frit to
death of multiple threads, and insist that the only way of reliably creating
apps that do more than one thing at one is to use looping, polling or
multiple processes, (AKA multiple threads that can only communicate with
each other slowly and inefficiently).

Rgds,
Martin

Replies

In response to

Re: Disconnect TIdHttp in thread posted by Karsten on Wed, 27 Jul 2005