3

I'm writing some software that talks to external hardware via a dll (moving some motors and reading some values back). The calls to the dll are blocking and may not return for in the order of 10 seconds. The software performs a scan by moving the hardware, taking a reading and repeating for a number of points. One scan can take in the order of 30 minutes to complete. While the scan is running I would obviously like the GUI to be responsive and a live graph (in an MDI Child) of the incoming data to be updated at each point. Multithreading seems the obvious choice for this problem.

My question is, what is the best way to thread this and talk back to the main VCL thread to update the graph during a scan?

I currently have a single TThread descendant that performs the 'scan logic' and an array of doubles in the public var section of the ChildForm. I need to fill out this array from the thread but I don't know whether to use Synchronize or CriticalSection or PostMessage or some other method. Each time a new value is added, the main VCL thread needs to update the graph. Should I really have an intermediary object for the data that is a global var and access this from the Thread and the ChildForm separately somehow?

Steve Magness
  • 863
  • 14
  • 25
  • 1
    Lots of options. Who are we to say which is best? `Synchronize` will work and is by a distance the easiest option. Let the main thread own the data and have the worker thread send new data using `Synchronize` for the main thread to append and display. – David Heffernan Oct 01 '13 at 10:56
  • What I'm trying to get to is a scalable solution, ie what happens when my scan can be performed multiple times and each scan has extra properties (color, title, noPoints) etc - where should this logic live, inside the thread or should the thread be as simple as possible and just perform a single measurement even? – Steve Magness Oct 01 '13 at 11:10
  • Are all your requirements going to be specified in the comments? – David Heffernan Oct 01 '13 at 11:11
  • Sorry, I think I'm still a little unclear in my own head what the 'real' question is - it's a more general 'architecture' thing than a simple question. I rewrote the post a few times trying to work out what it was I needed to ask but I need to get the ball rolling somehow... – Steve Magness Oct 01 '13 at 11:14
  • You can use anonymous functions with `TThread.Synchronize` and `TThread.Queue` and make it quite maintainable and readable. I really don't fancy the very low-level approach suggested by Ghigo. – David Heffernan Oct 01 '13 at 11:17
  • Which version of Delphi? – GolezTrol Oct 01 '13 at 12:14

4 Answers4

6

The simplest way to update the GUI from a thread is to use anonymous methods in conjunction with TThread.Synchronize and TThread.Queue.

procedure TMyThread.Execute;
begin
  ...
  Synchronize(  // Synchronous example
    procedure
    begin
      // Your code executed in main thread here 
    end
  );
  ...
  Queue( // Asynchronous example
    procedure
    begin
      // Your code executed in main thread here
    end
  );
end;

Passing values asynchronous often requires "capturing" a value.

procedure TMyThread.PassAValue(anInteger: Integer);
begin
  Queue(
    procedure
    begin
      // Use anInteger in main thread 
    end
  );
end;

procedure TMyThread.Execute;
var
  myInt: Integer;
begin
  ...
  PassAValue(myInt);  // Capture myInt
  ...
end;

When an anonymous method is using a variable, the reference to the variable is captured. This means that if you alter the variable value before the anonymous method is executed, the new value is used instead. Hence the need to capture the "value".

A more elaborate example can be found here, synchronize-and-queue-with-parameters, by @UweRaabe.

Community
  • 1
  • 1
LU RD
  • 34,438
  • 5
  • 88
  • 296
1

If you want to invest a little more then a simple Synchronize call which by the way blocks the main thread, you can add a simple FIFO queue with messaging on top of it. The flow of data would be like this:

  1. The thread puts the data into the queue.
  2. The thread post a message to the main thread window. Which one I don't care :)
  3. You handle the message that data is available and process any messages in the queue as you see fit.

The code would look something like this:

the queue...

const
  WM_DataAvailable = WM_USER + 1;

var
  ThreadSafeQueue: TThreadSafeQueue;

the data is put into the queue...

procedure PutDataIntoQueue;
var
  MyObject: TMyObject;
begin
  MyObject := TMyObject.Create;
  ThreadSafeQueue.Enqueue(MyObject);
  PostMessage(FMainWindowHandle, WM_DataAvailable, 0, 0);
end;

and processing...

procedure ProcessDataInTheQueue(var Msg: TMessage); message WM_DataAvailable;

procedure ProcessDataInTheQueue(var Msg: TMessage);
var
  AnyValue: TAnyValue;
  MyObject: TMyObject;
begin
  while ThreadSafeQueue.Dequeue(AnyValue) do
  begin
    MyObject := TMyObject(AnyValue.AsObject);
    try
      // process the actual object as needed
    finally
      MyObject.Free
    end;
  end;
end;

The code is written without Delphi and checks so it can contain errors. I showed the example using my freely available thread safe queue and TAnyValue. You can find both here:

http://www.cromis.net/blog/downloads/

Also please note then I did not do any check if PostMessage was actually sent. You should check that in production code.

Runner
  • 6,073
  • 26
  • 38
1

I find that populating a TThreadList from the background thread, then posting a message to the main thread that there is a new item in the list, then processing the list in the main thread is simple and easily maintainable.

With this method, you could store as many readings as you wanted in the list, and every time the main thread received a message, it would simply process all the items in the list at once.

Define a class for the readings, instantiate them, and add them to the list in the background thread. Don't forget to free them in the main thread when you pop them off the list.

Marcus Adams
  • 53,009
  • 9
  • 91
  • 143
0

Use postmessage inside you thread and send messages to main form handle. Register one (or more) custom messages and write a handler for them.

const WM_MEASURE_MESSAGE = WM_USER + 1;

Create a thread class, add a MainFormHandle property (Thandle or cardinal). Create thread suspended, set MainFormHandle with main form handle, then resume thread. When you have a new measure, assign data1 and data2 dword with some data from measure, then

PostMessage(fMainFormHandle,WM_MEASURE_MESSAGE,data1,data2);

In main form you have message handler:

procedure MeasureMessage(var msg: TMessage); message WM_MEASURE_MESSAGE;
begin
  // update graph here
  // msg.wparam is data1
  // msg.lparam is data2
end;

If you need to send much more data from thread to main form, you can create an appropriate structure in main context for the whole measurement data, pass a reference to thread, let the thread write data and use messages just to tell main form new data position (an array index, for example). Use TThread.Waitfor in main context to avoid freeing data structure while thread is still running (and writing into memory).

Ghigo
  • 2,312
  • 1
  • 18
  • 19
  • No need to create a suspended thread. Pass the window handle to the thread's constructor. However, you need to be 100% sure that the main form will not have its window handle recreated. Personally I'd use `AllocateHwnd` if I were doing this. But I wouldn't even do this. I'd use `TThread.Queue` and avoid re-implementing functionality that ships with the product. – David Heffernan Oct 01 '13 at 11:11
  • I don't like TThread.Synchronize approach because it will stop the running thread while executing main context code. I don't like TThread.Queue approach too, because when thread is finished, you lose all unprocessed calls, because thread queue is emptied when thread is finished. Moreover, TThread.Queue is not thread safe so you have to be careful. – Ghigo Oct 01 '13 at 11:50
  • `TThread.Synchronize` is indeed synchronous. If one needs asynchronous then it's obviously no good. Given the context, synchronous may be perfectly adequate. Anyway, let's stick to asynchronous for this discussion. I see no evidence to back up your other claims about `TThread.Queue`. Do you have any? I see no reason why the queue is emptied when the thread terminates. And I see no reason for `TThread.Queue` not being threadsafe. Please elaborate. – David Heffernan Oct 01 '13 at 12:07
  • What will you send in data1 and data2? If these are pointer, you must make sure that they will remain valid. This means that the thread will have to create them and the main form handling the message needs to clean them up. I think that's not a very good approach. – GolezTrol Oct 01 '13 at 12:13
  • data1 and data2 are not pointers but dwords. If you need to update X and Y coordinates on a graph, they're enough. – Ghigo Oct 01 '13 at 12:16
  • @David I went deep with TThread.Queue and I found this: [link](http://sergworks.wordpress.com/2010/09/14/synchronization-in-delphi-tthread-class) With TThread.Queue you have to pay attention if you use variables written by the thread. If thread updates variables before Queue method is executed, you lose data. If you need to update a progress bar, TThread.Queue is 100% ok. I won't trust TThread.Queue in more complex designs. Just my opinion, I could be (easily) wrong. – Ghigo Oct 01 '13 at 12:23
  • @Ghigo Nothing Serg wrote appears to back up your assertions. Perhaps your code was wrong! – David Heffernan Oct 01 '13 at 12:33
  • @Ghigo. I added an answer where all data is always preserved the minute it is posted into the queue. But I suspect that should also be the case with TThread.Queue. Can you make a test case to prove your claim? – Runner Oct 01 '13 at 13:24
  • @David. I agree with using Queue unless you are still on older Delphi versions where it was not available sadly. – Runner Oct 01 '13 at 13:25
  • I will further investigate my TThread.Queue usage, probably I made some mistake somewhere. Thanks for all suggestions. – Ghigo Oct 01 '13 at 13:58