This now appears to work. But, now there can be an up to 10 second delay before the application stops running after the form closes. Now this is fictional example that has just a single sleep() call. This can occur with real world items as well, but often there are several steps in the method, so I am going to simulate multiple steps, with a loop 0..9 with a call to sleep(1000);

Now the fictional example show multiple steps. But it does not solve the problem with the application running for up to 10 seconds after the main form is closed. When the form is begin closed the Task needs to be notified so it can stop running. This can be be done with the ITask.Cancel method.

To resolve this a third iteration is produced.

Task : ITask; has been moved from SlowProc, and is now a member of the form.

This all appears to work and is released. Sometime later in real world strange behaviors and errors are reported on this screen. After research it is learned that the GUI is not thread safe, so we use a TThread.Queue, to the GUI code to run in the main thread.

Now we have an finally application that should work without error. Granted this a fictional example, but it shows just some of the pitfalls that can come with multi-threading. Each is relatively easy to deal with.

11 comments:

I have just seen it done that way and never thought to ask that question.

After doing a code review, it appears there is a slightly different behavior in both. Ultimately both call TThread.Synchronize(ASyncRec: PSynchronizeRecord; QueueEvent: Boolean = False)

QueueEvent = true when using Queue, otherwise the default is false which is what the other typical synchronize method use.

I am not 100% sure but it appears the Synchronize will pause the current thread waiting a INFINITE amount of time for the code to execute. Queue will place it in the queue to be run and not then return.

The documentation http://docwiki.embarcadero.com/Libraries/XE7/en/System.Classes.TThread.QueueMentions that: "You can use nil/NULL as the AThread parameter if you do not need to know the information of the caller thread in the main thread."

Given this example, where I don't need to reference the original thread, it's not required.

Use messages to communicate between threads and components (good old PostMessage and message procedures). Object reference are problematic, either because the objects can be freed, or in a GC/ARC because they will retain references.A message will just be discarded automatically if its target is gone.As a further step, you also need tho provision against exceptions, and give an ability to cancel a task before it has been completed.

In the end, good tasks are either complex hand-made, case-specific tailored affairs, or ... scripts! A good script engine will allow to cancel any task at any time, will wrap exceptions and will naturally keep script-side references distinct from those Delphi-side through sandboxing.