tags:

views:

309

answers:

4

There are two procedures, A1 and A2, which both call function B in their code:

function B: boolean;
begin
  // do other stuff
end;

procedure A1;
begin
  // do stuff
  if b then
    ...
  // do stuff
end;

procedure A2;
begin
  // do stuff
  if b then
    A1; // <- how to call A1 "delayed"?
  // do stuff
end;

If the condition in A2 is true, procedure A1 must be called, but that would happen while A2 is still running, which I don't want.

What should happen is: If the condition in A2 is true, then A2 should be finished and after leaving A2 the procedure A1 should be called.

An ugly solution would be to set a timer that calls A1 after a delay that makes sure A2 is finished.

But there must be better ways, right?

EDIT: A1 and A2 in my case are events, so they are not called by code and I cannot just call A1 from a calling procedure after A2 is finished.

+3  A: 

Editing since you edited the question to say A1 and A2 are event handlers. You did not mention what kind of event handlers are these but if they are VCL event handlers, trigger that event by sending whatever WM_COMMAND necessary. If they are Windows events trigger the event using SetEvent().

ssg
ssg: The only way to do it this way and call A1 outside of A2 would again be a timer to trigger it. I didn't mention that A1 and A2 in my case are events, so there is no code that calls A2 after which I could call A1. (I'll add this to the question)
Holgerwa
Ok updated my answer.
ssg
+11  A: 

Given your constraints it sounds like you want to call PostMessage as the last thing in A2 before returning. The PostMessage parameters should be set up so trigger the A1 event. The only thing to worry about it a race condition, but I'm not sure that'll be an issue with the windows message queue.

dwc
I don't believe there would be a race condition. PostMessage is the way to go. The message queue would be processed once the A2 event handler completes or a manual call is made to Application.ProcessMessags.
Jim McKeeth
A: 

You could use a boolean flag: set "A1_should_run" if A2 is running, and check the flag at the end of A2. This solution is simple, but kindof ugly because it adds non-locality to the code and doesn't apply very well to more complex situations.

You could use a call queue: A1 and A2 are always run by being placed on the queue, in which case you simply enqueue A1. This solution is not as simple for your case, but applies very well to more complicated situations.

Strilanc
A: 

I have handled similar problems in the past by creating a delayed method call unit. It may be overkill if you just need it in one place but it is a useful thing to have.

In my case, I would write

procedure A2;
begin
  // do stuff
  if b then
    DelayedCall('A1' {identifier}, A1 {event}, 0 {ms delay}, [] {various function flags}); 
  // do stuff
end;

Internally DelayedCall places A1 onto an ordered list (ordered by time due). A timer polls the list (at a variable frequency depending on when the first event is due) and runs any due events.

It's not a terribly complicated function to write but it can be very useful.

Edit: In the example provided, using a PostMessage would run sooner. The DelayedCall wouldn't be run until after A2 has finished (unless A2 has a ProcessMessages or Sleep call in there) but it may not run immediately after A2 finished depending on what else is going on. However I frequently use a DelayedCall in similar scenarios myself.

Neither PostMessage or DelayedCall will happen immediately, they both need to wait till the message loop is checked

SeanX
The problem with using a timer is that you never know what else is going on in the system and when A2 will be finished. PostMessage will put the call right behind the end of event A2, when the message queue is handled next time.
Holgerwa