3

I have a function which must run completely, without interruption, and return a result. If an asynch event causes it to be called again while it is still executing, that call must be blocked somehow until the first call has completed.

Mutex? Something else?


[Update] The function is in my main form class and is invoked from two methods of the class: one handles data read from the serail port and the other handles timer expiry. Both of these appear to be running in their own threads as a call of the function from one can be interupted by a call from the other (to me, at application level, they are just components which I dropped on my main form at design time).

Maybe TCriticalSection? (but googling makes it unclear whether I need to use acquire/release or 'enter/leave` and it also seems that since the code is in a simple function of may mainform, it will be reentrant).

Perhaps what I am asking for is a way to make code "un-reentrant", blocking until the firsty entry is complete? Whatever the answer, I think that I am going to need a code exampel, or a UTl to one :-(

(Note that this page gives lots of useful information (I don't grok it all, but it looks extremely useful to others))


[Update] This has nothing to do with GUI updating, just that each event that sends some TCOP data should recevie the response.

Although the app has a GUI - it is form based - that is only in case I wanted to show some debug info, becasue the app will run on a PC with no monitor (yes, I know that it stil has a GUI, but that is not my problem/point)

BenMorel
  • 34,448
  • 50
  • 182
  • 322
Mawg says reinstate Monica
  • 38,334
  • 103
  • 306
  • 551
  • 1
    Something like this? http://www.delphicorner.f9.co.uk/articles/op4.htm – Patrick Savalle Aug 15 '12 at 13:48
  • 1
    Also: http://stackoverflow.com/questions/8101711/delphi-preferred-way-of-protection-using-critical-sections – Patrick Savalle Aug 15 '12 at 13:49
  • 1
    Please specify your question. Assuming while your function was running, the async event happened 3 times. What should be done after function completes ? Those events dismissed and function not called again ? Those events postponed and function called 3 times ? Those events aggregated and function called 1 time ? – Arioch 'The Aug 15 '12 at 13:56
  • 1
    Are you talking about a function which runs in the main thread only? Or a function which is run from >1 threads and must be re-entrant plus atomic? – Warren P Aug 16 '12 at 00:12
  • 1
    Functions can only be interrupted if you let them. Are you calling ProcessMessages. That's the classic way to make an event handler re-entrant. As I said in my answer, using a lock will just deadlock your app, so forget all about that idea. – David Heffernan Aug 16 '12 at 07:04
  • 1
    he might use some VCL functions that can call ProcessMEssages or somethign internally. Like ShowModal, etc. Even if he does not call some kind of yield-function explicitly, in copmplex funcitons he can not be sure it is not called indirectly. OTOH whay would one make big complex function in an async-called yet non-reentrant way? – Arioch 'The Aug 16 '12 at 08:03
  • 1
    @Mawg Thanks for update. But still it is not clear how should intermediate calls be managed. dismissed, postponed or aggregated ? Also what are the sense of those calls. RS232-call should work over the data received. What data should timer-triggered call work over ? – Arioch 'The Aug 16 '12 at 08:06
  • @david (+1) I am not calling ProcessMessage(), but, like Arioch says, I can imagine that a timer expiry can interup nornal processing and the RS232 component probably runs in its own thread. I Know that I do not call ProcessMessage(), but I do not know enough about threading to say more – Mawg says reinstate Monica Aug 16 '12 at 11:22
  • @Arioch (+1) Thanks. I would just like all calls to be FIFO queued & executed. None to be dismissed – Mawg says reinstate Monica Aug 16 '12 at 11:23
  • 1
    Timers cannot interrupt execution. They fire when the message queue is pumped. You need to work out why your method is re-entrant. At the moment you don't know why. Until you know you are stuck. – David Heffernan Aug 16 '12 at 11:45
  • Your background threads should not invoke any methods which COULD be invoked by the foreground thread. Instead, you can easily make things work by just using PostMessage from the background thread when it completes an action. – Warren P Aug 16 '12 at 12:18
  • If you talk about standard TTimer - when it does it just posts the message to window. IBEvents component has several threads waiting for event, but then it "flattens" them to main thread by doing PostMessage. And your form's method would not be interrupted until form can peek next message form queue. Which is only done after function exit or via ProcessMessages (like in ShowModal). So i'm with David on that - you function seems having no reasons to need reenterability. You probably do not have threads. You don't know why your function is called from inside her own. Check call stack and fix it. – Arioch 'The Aug 16 '12 at 14:47
  • you seems at risk of having recursion until stack overflow. Also - i updated my answer at morning exactly about FIFO queues. Did you check out ? – Arioch 'The Aug 16 '12 at 14:49

4 Answers4

7

Move the function to a separate worker thread where there are no async events.

Async event in main thread should post some message into some queue, so when the worker thread complete execution, the main thread would start it once again with new parameters.


There was update with some more info. Assuming (guessing) that the functions works over received data over RS232 or over nothing, and is doing something like GUI update, i'd sketch the following approach.

  1. use Windows messaging queue of the form for alerting.
  2. use some interlocked queue objects for data passing. (yes, i know i can put pointers into Windows message, but i'd like a bit more type safety)
  3. use external working thread for long processing.

More details:

  1. grep VCL sources for WM_USER and see the pattern. You declare 3 message id constants: Work_Complete, Work_Request, Work_EmptyQueue. And correspondent message-methods in form.
  2. I'd use ready thread-safe queue from OmniThreadLibrary. But you may subclass System.Contnrs.TQueue or System.Generics.Collections.TQueue, wrapping making all their data-passing methods into critical section. Overall i suggest you to loo at OmniThreadLibrary pipelines if they can suite you
  3. that is standard way of hardware data working apps. Be it MS-DOS device drivers. Or some embedded device. You should separate fast and lean data saving from long working.

So, the RS232 event handler would read data from COM, put it into TBytes, and then add that packet to input TQueue. A bit more complex approach would be to look if Queue already contains data from COM and aggregating new packet with old rather than patting new packet as separate. That would require more careful locking, so aggregating here maybe just does not worth the candles

Timer even would make empty packet (zero-length bytes array) and enqueue it similarly. if that timer even also has some data to pass - then it should be variant record or separate input queue or whatever. But without information it seems timer just sends no info but the alert itself

According to your words both Timer and RS232 events work in their separate threads. I have doubts about that but i have to trust you.

After enqueuing the workload (for example in the input queue's notify event) i'd do win32 PostMessage(MainForm.Handle, work_request). Afterall we'd like to centralize threads control in one place. To keep threads isolated message should be posted, no SendMessage no TControl.Perform!

In main thread in the form's work_request handler i'd look if the input queue is not empty already. If not, then i'd look at the worker thread state. If it is suspended, i'd resume it.

worker thread would look is input queue has something, and while it has, it will:

  1. extract the packet from the queue into local var
  2. do some potentially long processing
  3. make output "work results" packet. Assuming that is GUI updating that may be just a collection of key-value pairs, or some record of data fields with the set which data fields were filled and which are to be ignored.
  4. enqueue that packet into output queue (queue then post Work_Complete msg to main form)
  5. loop to 1.

if the input queue is empty, then the function exits, the thread does PostMessage Work_EmptyQueue and suspends itself until later would be awaken to do more work or freed.

When MainForm (in main thread again) receives Work_Complete it

  1. extracts all packets from the output queue into local vars
  2. merge them. (if i have p1=(label1='aaaa', label2='bbbb') and p2=(label3='cccc', label1='dddd') then the accumulated task would be setting (label1='dddd', label2='bbbb', label3='cccc');
  3. apply them (actually update GUI)

steps 2 and 3 are omitted if output queue is empty (it will be if merging happened on previous step). But not 4th. steps 1 and 2 are separate. Since queue operations are thread-interlocking, the goal of step 1 is to extract data as fast as possible. Margine is to be done locally.

When MainForm receives Work_EmptyQueue it checks if input queue is not empty now and maybe resumes the worker thread. It optionally may do some GUI updating of status or whatever.

That is a rough sketch. It can be carved better to you certain cituation.

Arioch 'The
  • 15,799
  • 35
  • 62
  • 1
    This is the "stateless" approach I like very much. Very easy to code and to debug. Windows itself is founded on such message queues, so re-using a GDI message for UI update is always a good idea. But the OP did not speak about UI, so perhaps it won't help in his case. – Arnaud Bouchez Aug 15 '12 at 14:43
  • 1
    Neither did i ;-) If he uses TForm - then he is free to use Win32 PostMessage But if not - he is free to use TQueue or whatever queue he'd like :-) – Arioch 'The Aug 15 '12 at 16:50
  • Well, you still did not targeted some questions. You described a bit what is happened, but not why and not how. That would make all the questions just blind guesses. If you want answers to match exactly your situations - describe those parts that we are not sure and keep asking about. – Arioch 'The Aug 16 '12 at 08:11
7

Assuming that you are concerned about re-entrancy from the same thread, then blocking the re-entrant call will lead to a classic deadlock. If this is your scenario then you need to do one of the following:

  1. Make sure that re-entrant calls cannot happen, or
  2. Detect a re-entrant call and postpone it by adding to a queue for later processing, or
  3. Detect a re-entrant call and simply ignore it.

Quite possibly none of these options appeals to you!

If you are concerned about simultaneous calls from different threads then you can use a lock of some sort. For example on Windows you would typically use a critical section.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • (+1) That is, indeed, the case (concerned about re-entrancy from the same thread) and it looks like I will have to go with 2. Do I have to code my own mechanism? is there no Delphi or Windows help for me, or maybe a VCL component? – Mawg says reinstate Monica Aug 16 '12 at 22:58
  • 1
    I'm not aware of any off the shelf components to implement my option 2. I still feel that you should look hard at option 1. It's not obvious why you should be getting re-entrancy. I'd urge you to understand that first. – David Heffernan Aug 17 '12 at 07:02
5

Use a combination of two mechanisms:

  1. Use a TCriticalSection to protect against entry by another thread. Obviously this is only necessary multi-threaded applications.
  2. Use a re-entry gate. This is a simple boolean to prevent re-entry from the same thread. If the function you are trying to protect is already entered, then do not block, but bounce. 'Bounce' means take some appropriate action such as queuing the required activity for later execution.
Sean B. Durkin
  • 12,659
  • 1
  • 36
  • 65
1

Another approach is when entering the critical function from one of the event sources (say, serial port handler), disable the other event source (expiry timer) for the duration of the call. Reenable it after the call. You're guaranteed that the other event source cannot fire during your call because the timer has been disabled.

This is braindead simple for the case of turning off the expiry timer when you're about to process a serial data packet. If we assume that the expiry timer is to track when nothing has been received from the serial port for some period of time, disabling the expiry timer on serial data received is appropriate. Restore/restart the expiry timer when finished processing the serial data packet.

The opposite case - receiving serial data while processing the expiry timer - may be a little trickier, since you probably don't want to risk losing data by disabling the serial data receiver. If your serial data handler can easily be paused/resumed with no loss of data, you're done! Otherwise, your best bet here is probably to use a boolean flag in the object instance to indicate when the critical function is executing.

If a serial data packet comes in and that flag is true, then push the serial data packet into a list to be processed later and return immediately. Set up your expiry timer handler to check that list immediately before and after returning from the critical call. If the list is non-empty, process all the items in the list. If there are data packets in the list waiting to be processed as you enter the expiry timer handler, you probably want to ignore the expiry timer expiration and just keep on chugging.

In the serial data handler, check if the list is non-empty before and after calling the critical function. Checking before will help ensure that the serial data packets are handled in the order they were received. Checking after will ensure that subsequent serial data that arrives during the processing of the current data packet will not be lost.

If there is any chance of the serial data event handler being called on a different thread, then updates to the boolean flag and list should be handled carefully in a threadsafe manner. If the serial event handler (and the expiry timer handler) are guaranteed to execute on the UI thread, then you don't have to worry about this.

dthorpe
  • 35,318
  • 5
  • 75
  • 119