Tasking: Very slow response - task

My actual program creates a task which I does not react to control messages as it should. As it has grown pretty large, I present a short test program with the same control logic. It creates a background task which repeates a loop every 0.1s. Depending on a protected flag "running" it prints out an 'a', or does nothing. When I set "running", the program goes off immediately, printing the 'a's. But when I set "stopreq", it takes seconds, sometimes well over 10, until it stops. I would expect a response time of 0.1 or 0.2s.
Anybody has an explanation and a solution?
My main program opens a window with 3 buttons: "Start", which calls the subprogram Start below, "Stop", which calls Request_Stop, and "Quit" calling Request_Quit. I am working on a PC running Linux.
Here comes the body of my Tasking package. If you need more, just tell me and I post the other parts.
with Ada.Text_IO;
with Ada.Calendar;
package body Tasking is
t_step: constant Duration:= 0.1;
dti: Duration;
protected Sync is
procedure Start; -- sim. shall start
procedure Request_Stop; -- sim. shall stop
procedure Stop_If_Req;
function Is_Running return Boolean; -- sim. is running
procedure Request_Quit; -- sim.task shall exit
function Quit_Requested return Boolean; -- quit has been requested
procedure Reset_Time;
procedure Increment_Time (dt: Duration);
procedure Delay_Until;
private
running: Boolean:= false;
stopreq: Boolean:= false;
quitreq: Boolean:= false;
ti: Ada.Calendar.Time;
end Sync;
protected body Sync is
procedure Start is begin running:= true; end Start;
procedure Request_Stop is
begin
if running then stopreq:= true; end if;
end Request_Stop;
procedure Stop_If_Req is
begin
if stopreq then
running:= false;
stopreq:= false;
end if;
end Stop_If_Req;
function Is_Running return Boolean is begin return running; end Is_Running;
procedure Request_Quit is begin quitreq:= true; end Request_Quit;
function Quit_Requested return Boolean
is begin return quitreq; end Quit_Requested;
procedure Reset_Time is begin ti:= Ada.Calendar.Clock; end Reset_Time;
procedure Increment_Time (dt: Duration) is
begin
ti:= Ada.Calendar."+"(ti, dt);
dti:= dt;
end Increment_Time;
procedure Delay_Until is
use type Ada.Calendar.Time;
now: Ada.Calendar.Time;
begin
now:= Ada.Calendar.Clock;
while ti < now loop -- while time over
ti:= ti + dti;
end loop;
delay until ti;
end Delay_Until;
end Sync;
task body Thread is
begin
Ada.Text_IO.Put_Line("starting task");
while not Sync.Quit_Requested loop
if sync.Is_Running then
sync.Increment_Time (t_step);
sync.Delay_Until;
Ada.Text_IO.Put("a");
sync.Stop_If_Req;
else
delay t_step;
sync.Reset_Time;
end if;
end loop;
end Thread;
procedure Start is
begin
Sync.Start;
end Start;
function Is_Running return Boolean is
begin
return Sync.Is_Running;
end Is_Running;
procedure Request_Stop is
begin
Ada.Text_IO.Put_Line("");
Sync.Request_Stop;
end Request_Stop;
procedure Request_Quit is
begin
Sync.Request_Quit;
end Request_Quit;
end Tasking;

Your code is too poorly described and commented for me to understand what you want it to do.
But using a "delay until" statement in a protected operation (Sync.Delay_Until) is not correct -- it is a "bounded error". If it works, it probably blocks all other calls to that protected object until the delay has expired. I suggest you should start there when you try to correct the code.

Thanks to your comments, the program is working correctly now.
To Jesper Quorning:
"Time in past" was not a problem here, but could be so in my real software project. I included a remedy nevertheless in the corrected version below, so it could serve as a pattern for others. Thanks for the hint.
To Niklas Holsti:
I shifted "delay until" out of the protected operation and the program now works as expected, thanks for the explanation. (Sometimes a program which works badly is worse than one which doesn't work at all, because in the latter case you take compiler warnings more seriously.)
What I want to do is:
Execute an application at precisely defined time intervals
Being able to enable/disable it anytime
Giving it time to stop only when in a well defined state
Knowing, when it really has stopped
Controlled termination of the whole thing.
My idea of realization was:
Call the application in an endless loop as separate task
Check whether exit has been requested at the top of the loop
An if statement which executes the application if enabled
Simple delay otherwise.
Control from other packages is done with the public subprograms
Start, Is_Running, Request_Stop, Request_Quit.
Besides the corrections mentioned above, I renamed some items to make their role clearer.
If there is a standard solution, different from mine, I would like to see it.
Here comes my corrected program:
with Ada.Text_IO;
with Ada.Calendar;
package body Tasking is
t_step: constant Duration:= 0.1;
protected Sync is
procedure Enable_App; -- enable application
procedure Request_Disable; -- request disabling of app.
procedure Disable_If_Req; -- disable app. if requested
function Enabled return Boolean; -- application is enabled
procedure Request_Quit; -- task shall terminate
function Quit_Requested return Boolean; -- task termination requested
procedure Set_Time (t: Ada.Calendar.Time); -- set execution time
function Get_Time return Ada.Calendar.Time; -- get execution time
private
running: Boolean:= false; -- application is enabled
stopreq: Boolean:= false; -- disabling has been requested
quitreq: Boolean:= false; -- task termination requested
ti: Ada.Calendar.Time; -- time of next app. execution
end Sync;
protected body Sync is
procedure Enable_App is begin running:= true; end Enable_App;
procedure Request_Disable is
begin
if running then stopreq:= true; end if;
end Request_Disable;
procedure Disable_If_Req is
begin
if stopreq then
running:= false;
stopreq:= false;
end if;
end Disable_If_Req;
function Enabled return Boolean is
begin return running; end Enabled;
procedure Request_Quit is
begin quitreq:= true; end Request_Quit;
function Quit_Requested return Boolean
is begin return quitreq; end Quit_Requested;
procedure Set_Time (t: Ada.Calendar.Time) is
begin ti:= t; end Set_Time;
function Get_Time return Ada.Calendar.Time is
begin return ti; end Get_Time;
end Sync;
task body Thread is
use type Ada.Calendar.Time;
now: Ada.Calendar.Time;
begin
Ada.Text_IO.Put_Line("starting task");
while not Sync.Quit_Requested loop
if sync.Enabled then
-- increment time if it is too late
now:= Ada.Calendar.Clock;
while sync.Get_Time <= now loop
sync.Set_Time (sync.Get_Time + t_step);
end loop;
-- wait until next execution time
delay until sync.Get_Time;
-- execute application and set time for next execution
Ada.Text_IO.Put(".");
sync.Set_Time (sync.Get_Time + t_step);
-- disable application if this has been requested
sync.Disable_If_Req;
else
-- wait for enabling and set time for next execution
delay t_step;
sync.Set_Time (Ada.Calendar.Clock + t_Step);
end if;
end loop;
end Thread;
procedure Start is
begin
Sync.Enable_App;
end Start;
function Is_Running return Boolean is
begin
return Sync.Enabled;
end Is_Running;
procedure Request_Stop is
begin
Ada.Text_IO.Put_Line("");
Sync.Request_Disable;
end Request_Stop;
procedure Request_Quit is
begin
Sync.Request_Quit;
end Request_Quit;
end Tasking;

Some general ideas about Ada tasking:
Conceptually, an Ada task has its own processor that no other task runs on. Scheduling, such as determining when the task should next do something, is usually the task's responsibility.
When the task has to wait for an event, it is usually best for the task to block than to poll.
Ada.Calendar is typically local time, and can even go backward. Tasks should schedule themselves with Ada.Real_Time if possible.
So I would do something like
protected Control is
procedure Set_Running (Running : in Boolean := True);
-- Set whether the task should run or not
-- Initially Running is False
entry Wait_Until_Running;
-- Blocks the caller until Running is set to True
function Running return Boolean;
-- Returns the current value of Running
procedure Quit_Now;
-- Tell the task to quit
function Quitting return Boolean;
-- Returns True when Quit_Now has been called; False otherwise
private -- Control
Run : Boolean := False;
Quit : Boolean := False;
end Control;
task body T is
Step : constant Ada.Real_Time.Time_Span := Ada.Real_Time.Milliseconds (100);
Next : Ada.Real_Time.Time;
begin -- T
Forever : loop
Control.Wait_Until_Running;
Next := Ada.Real_Time.Clock;
Run : while Control.Running loop
exit Forever when Control.Quitting;
delay until Next;
Next := Next + Step;
-- The task does its thing here
Ada.Text_IO.Put (Item => 'a');
end loop Run;
end loop Forever;
end T;
protected body Control is
procedure Set_Running (Running : in Boolean := True) is
begin
Run := Running;
end Set_Running;
entry Wait_Until_Running when Run is
begin
null;
end Wait_Until_Running;
function Running return Boolean is (Run);
procedure Quit_Now is
begin
Run := True; -- Release the task if it is blocked on Wait_Until_Running
Quit := True;
end Quit_Now;
function Quitting return Boolean is (Quit);
end Control;

Thanks for your new description of the goals, #hreba, and good to hear that your program is working as expected now.
I agree with #Anh Vo that the application task could be controlled with task entries, but I think that using a protected object (PO) is mostly preferable because it decouples the caller (the main task) from the application task -- it does not make the caller wait until the application task is ready to accept the entry call. However, I would reduce the PO to handle the inter-task communication. In the #hreba code, I see no reason for the protected operations Set_Time and Get_Time, as the timing seems completely controlled by the application task anyway.
Here is how I would program this. First, instead of a number of Booleans I would define an enumeration for the state of the application task:
type App_State is (Enabled, Disabled, Stopping, Stopped);
In the #hreba code, it seems the application task is initially "not running", so
Initial_State : constant App_State := Disabled;
The PO would then be declared as (I leave out the comments because I will comment in prose):
protected App_Control is
procedure Enable;
procedure Disable;
procedure Stop;
entry Get_New_State (New_State : out App_State);
entry Wait_Until_Stopped;
private
State : App_State := Initial_State;
Old_State : App_State := Initial_State;
end App_Control;
The PO operations Enable, Disable, and Stop do the evident things:
procedure Enable
is
begin
State := Enabled;
end Enable;
and analogously for Disable and Stop. Stop sets State to Stopping.
The entry Get_New_State will be called in the application task to receive the new state. It is an entry, instead of a function or a procedure, so that it can be made callable only when the state has changed:
entry Get_New_State (New_State : out App_State)
when State /= Old_State
is
begin
New_State := State;
Old_State := State;
if State = Stopping then
State := Stopped;
end if;
end Get_New_State;
Here I made an automatic transition from Stopping to Stopped as soon as the application task has been given New_State = Stopping. If the application task has to do some clean-up before it can be considered really stopped, that transition should be moved to a separate operation, for example procedure App_Control.Has_Stopped, to be called from the application task after that clean-up.
The entry Wait_Until_Stopped can be called in the main task to wait until the application task has stopped. It is very simple:
entry Wait_Until_Stopped
when State = Stopped
is
begin
null;
end Wait_Until_Stopped;
Now for the application task. My design differs in one main point from the original: to make the task more responsive to "stop" commands, I use a timed entry call of Get_New_State to let that call take place while the application task is waiting for its next activation time. This may not matter much if the task is running every 0.1 s, but in some other scenario the task might be running every 10 s, or every 10 minutes, and then it could matter a great deal. So here is the task body:
task body App_Thread
is
use Ada.Calendar;
Period : constant Duration := 0.1;
State : App_State := Initial_State;
Next_Time : Time := Clock + Period;
begin
loop
select
App_Control.Get_New_State (New_State => State);
exit when State = Stopping;
or
delay until Next_Time;
if State = Enabled then
Execute_The_Application;
end if;
Next_Time := Next_Time + Period;
end select;
end loop;
end App_Thread;

To uncouple between calling task and called task, do not use accept {entry name} do statement. Just break this into two separate statements as shown below with entry Start_Work for example
--coupling case
accept Start_Work do
Execute_The_Application
end Start_Work;
--uncoupling case
accept Start_Work;
Execute_The_Application;

Here is my implementation of #hreba's requirement.
package Tasking is
-- more codes
task Thread2 is
entry Start_Work;
entry Stop_Work;
entry Quitting_Time;
end Thread2;
task body Thread2 is
use type Ada.Real_Time.Time;
T_Step : constant Ada.Real_Time.Time_Span := Ada.Real_Time.Milliseconds (100);
Next_Time : Ada.Real_Time.Time := Ada.Real_Time.Clock;
begin
Ada.Text_IO.Put_Line("task Thread2 elaborates and activates");
Forever: loop
select
accept Quitting_Time;
Ada.Text_IO.Put_Line ("task Thread2 terminates. Later alligator");
exit Forever; -- later alligator
or
accept Start_Work;
Working: loop
Ada.Text_IO.Put_Line ("Ada");
Next_Time := Next_Time + T_Step;
select
accept Stop_Work;
exit Working;
or
delay until Next_Time;
end select;
end loop Working;
or
Terminate;
end select;
end loop Forever;
end Thread2;
-- more codes
end Tasking;

Related

"Emergency" termination of omnithread IOmniParallelTask

Background
I have a unit test in which I check if my handler code code performs well during multi-thread stress:
procedure TestAppProgress.TestLoopedAppProgressRelease_SubThread;
begin
var bt:=Parallel.ParallelTask.NumTasks(1).NoWait.Execute(
procedure
begin
SetWin32ThreadName('TestLoopedAppProgressRelease_SubThread');
RunLoopedAppProgressRelease;
end
);
lSuccess:=bt.WaitFor(cRunLoopTimerMilliSecs*2);
if not lSuccess then
bt.Terminate; // emergency termination, unit test failed <<< How do I do this?
Check(lSuccess,'Failed to finish within expected time');
end;
in case the parallel thread fails to complete within the expected time, something is wrong and my check fails.
Unfortunately if the parallel task hangs, it never gets to an end and my unit test freezes because of the release of the bt interface at the end of my routine that waits for the never ending parallel task to complete.
So I need to shutdown my parallel task the evil way.
NOTE 1: It's a unit test, I don't really care about the thread cleanup: something needs to be fixed anyway if the unit test fails. I just don't want my entire unit test suite to hang/freeze, but simply report the failure and to continue with the next test in my suite.
NOTE 2: The Numthreads(1) could be omitted or an arbitrary number of threads.
Here's the Q:How can I terminate an IOmniParallel task forcibly?
Okay, I should have done better research. Here's the solution for a single background thread, just use the IOmniTaskControl interface:
procedure TestAppProgress.TestLoopedAppProgressRelease_SubThread;
begin
var lTask:=CreateTask(
procedure (const aTask:IOmniTask)
begin
RunLoopedAppProgressRelease;
end,
'TestLoopedAppProgressRelease_SubThread'
);
lTask.Run;
var lSuccess:=lTask.WaitFor(cRunLoopTimerMilliSecs*2);
if not lSuccess then
lTask.Terminate; // emergency termination, unit test failed
Check(lSuccess,'Failed to finish within expected time');
end;
And in case you want to run it in multiple subthreads, you need to wrap it up in an IOmniTaskGroup:
procedure TestAppProgress.TestLoopedAppProgressRelease_MultiSubThread;
const cThreads=4;
begin
var lTaskGroup:=CreateTaskGroup;
for var i:=1 to cThreads do
begin
var lTask:=CreateTask(
procedure (const aTask:IOmniTask)
begin
RunLoopedAppProgressRelease;
end,
'TestLoopedAppProgressRelease_SubThread '+i.ToString
);
lTaskGroup.Add(lTask);
end;
lTaskGroup.RunAll;
var lSuccess:=lTaskGroup.WaitForAll(cRunLoopTimerMilliSecs*2);
if not lSuccess then
lTaskGroup.TerminateAll; // emergency termination, unit test failed
Check(lSuccess,'Failed to finish within expected time');
end;
Using delays/breakpoints in my debugger I verfied the unit test (error) handling now works as expected. This is including expected memory leaks due to the killed threads.
I still feel IOmniParallelTask should have an Terminate method similar to the ones in IOmniTask and IOmniTaskGroup

Ada select multiple entries

I'm looking for a way to select on multiple entries. I've got the following task and select block. The intention is to run multiple (2) tasks elsewhere, run until one completes, or timeout after some amount of time
task type t_startup_task is
entry start;
entry started;
end t_startup_task;
task body t_startup_task is
startup_sig : Boolean := False;
begin
accept start;
busy : loop -- wait for external flag to become true
status.read_signal (startup_sig);
if startup_sig then
exit busy;
end if;
delay 0.1;
end loop busy;
accept started;
end t_startup_task;
<...>
startup_task.start;
select
startup_task.started;
or
state.wait_done; -- other entry
abort startup_task;
return False;
or
delay 4.0;
end select;
However, this results in the following compile error:
only allowed alternative in timed entry call is delay
"or" not allowed here
What's the best way of actually doing this?
Unfortunately you can't use a select statement for deciding between multiple entry calls. You can however use them to decide between accepting entries, so an implementation with three tasks should work.
You can still have your return value by using an out parameter on the final entry call.
task startup_task is
entry start;
entry wait_done;
entry signalled;
entry started (success : out boolean);
end startup_task;
-- This task waits upon your other entry, then calls the startup_task
-- entry once it has completed
task startup_wait is
entry start;
end startup_wait;
task body startup_wait is
begin
accept start;
state.wait_done;
startup_task.wait_done;
end startup_wait;
-- This task contains your busy loop and calls the startup_task
-- entry once it has completed
task startup_signal is
entry start;
end startup_signal;
task body startup_signal is
begin
accept start;
busy : loop -- wait for external flag to become true
status.read_signal (startup_sig);
if startup_sig then
exit busy;
end if;
delay 0.1;
end loop busy;
startup_task.signalled;
end startup_signal;
-- This task provides the functionality of your invalid select statement,
task body startup_task is
success : boolean := False;
begin
-- These start signals ensure that the subtasks wait for synchronisation
accept start;
startup_wait.start;
startup_signal.start;
select
accept signalled;
abort startup_wait;
success := True;
or
accept wait_done;
abort startup_signal;
or
delay 4.0
abort startup_wait;
abort startup_signal;
end select;
accept started (success);
end startup_task;
<...>
result : boolean;
begin
-- this block replaces your invalid select statement
startup_task.start;
startup_task.started(result);
return result;
end;
Note, I have not tested or compiled this code, but it should give an idea towards a solution.
Technically, the language allows this:
select
delay 4.0;
...
then abort
select
Entry1;
...
then abort
Entry2;
...
end select;
end select;
which might do what you want. However, the best way is probably to have the tasks check in with a protected object, and wait on an entry of the PO:
protected Multi_Wait is
procedure Task1_Ready;
procedure Task2_Ready;
entry Wait_For_Either (Task_1 : out Boolean; Task_2 : out Boolean);
private -- Multi_Wait
Task1 : Boolean := False;
Task2 : Boolean := False;
end Multi_Wait;
Then your code can do
select
Multi_Wait.Wait_For_Either (Task_1 => Task_1, Task_2 => Task_2);
if not Task_1 and Task_2 then
abort T1;
return False;
end if;
or
delay 4.0;
end select;
Your tasks call the appropriate procedure instead of waiting for a 2nd entry call.

Why does the following code using IOmniThreadPool cause access violations?

In our Delphi XE4 application we are using an OmniThreadPool with MaxExecuting=4 to improve the efficiency of a certain calculation. Unfortunately we are having trouble with intermittent access violations (see for example the following MadExcept bug report http://ec2-72-44-42-247.compute-1.amazonaws.com/BugReport.txt). I was able to construct the following example which demonstrates the problem. After running the following console application, an access violation in System.SyncObjs.TCriticalSection.Acquire usually occurs within a minute or so. Can anybody tell me what I am doing wrong in the following code, or show me another way of achieving the desired result?
program OmniPoolCrashTest;
{$APPTYPE CONSOLE}
uses
Winapi.Windows, System.SysUtils,
DSiWin32, GpLists,
OtlSync, OtlThreadPool, OtlTaskControl, OtlComm, OtlTask;
const
cTimeToWaitForException = 10 * 60 * 1000; // program exits if no exception after 10 minutes
MSG_CALLEE_FINISHED = 113; // our custom Omni message ID
cMaxAllowedParallelCallees = 4; // enforced via thread pool
cCalleeDuration = 10; // 10 miliseconds
cCallerRepetitionInterval = 200; // 200 milliseconds
cDefaultNumberOfCallers = 10; // 10 callers each issuing 1 call every 200 milliseconds
var
gv_OmniThreadPool : IOmniThreadPool;
procedure OmniTaskProcedure_Callee(const task: IOmniTask);
begin
Sleep(cCalleeDuration);
task.Comm.Send(MSG_CALLEE_FINISHED);
end;
procedure PerformThreadPoolTest();
var
OmniTaskControl : IOmniTaskControl;
begin
OmniTaskControl := CreateTask(OmniTaskProcedure_Callee).Schedule(gv_OmniThreadPool);
WaitForSingleObject(OmniTaskControl.Comm.NewMessageEvent, INFINITE);
end;
procedure OmniTaskProcedure_Caller(const task: IOmniTask);
begin
while not task.Terminated do begin
PerformThreadPoolTest();
Sleep(cCallerRepetitionInterval);
end;
end;
var
CallerTasks : TGpInterfaceList<IOmniTaskControl>;
i : integer;
begin
gv_OmniThreadPool := CreateThreadPool('CalleeThreadPool');
gv_OmniThreadPool.MaxExecuting := cMaxAllowedParallelCallees;
CallerTasks := TGpInterfaceList<IOmniTaskControl>.Create();
for i := 1 to StrToIntDef(ParamStr(1), cDefaultNumberOfCallers) do begin
CallerTasks.Add( CreateTask(OmniTaskProcedure_Caller).Run() );
end;
Sleep(cTimeToWaitForException);
for i := 0 to CallerTasks.Count-1 do begin
CallerTasks[i].Terminate();
end;
CallerTasks.Free();
end.
You have here an example of hard-to-find Task controller needs an owner problem. What happens is that the task controller sometimes gets destroyed before the task itself and that causes the task to access memory containing random data.
Problematic scenario goes like this ([T] marks task, [C] marks task controller):
[T] sends the message
[C] receives the message and exits
[C] is destroyed
new task [T1] and controller [C1] are created
[T] tries to exit; during that it accesses the shared memory area which was managed by [C] but was then destroyed and overwritten by the data belonging to [C1] or [T1]
In the Graymatter's workaround, OnTerminated creates an implicit owner for the task inside the OmniThreadLibrary which "solves" the problem.
The correct way to wait on the task to complete is to call taskControler.WaitFor.
procedure OmniTaskProcedure_Callee(const task: IOmniTask);
begin
Sleep(cCalleeDuration);
end;
procedure PerformThreadPoolTest();
var
OmniTaskControl : IOmniTaskControl;
begin
OmniTaskControl := CreateTask(OmniTaskProcedure_Callee).Schedule(gv_OmniThreadPool);
OmniTaskControl.WaitFor(INFINITE);
end;
I will look into replacing shared memory record with reference-counted solution which would prevent such problems (or at least make them easier to find).
It looks like your termination message is causing the problem. Removing the message and the WaitForSingleObject stopped the AV. In my tests just adding a .OnTerminated(procedure begin end) before the .Schedule also did enough to change the flow and to stop the error. So the code in that case would look like this:
procedure PerformThreadPoolTest();
var
OmniTaskControl : IOmniTaskControl;
begin
OmniTaskControl := CreateTask(OmniTaskProcedure_Callee).OnTerminated(procedure begin end).Schedule(gv_OmniThreadPool);
WaitForSingleObject(OmniTaskControl.Comm.NewMessageEvent, INFINITE);
end;
It looks to me like this might be the problem. otSharedInfo_ref has a property called MonitorLock. This is used to block changes to otSharedInfo_ref. If for some reason otSharedInfo_ref is freed while the acquire is waiting then you are likely to get some very weird behavior
The code as it stands looks like this:
procedure TOmniTask.InternalExecute(calledFromTerminate: boolean);
begin
...
// with internal monitoring this will not be processed if the task controller owner is also shutting down
sync := nil; // to remove the warning in the 'finally' clause below
otSharedInfo_ref.MonitorLock.Acquire;
try
sync := otSharedInfo_ref.MonitorLock.SyncObj;
if assigned(otSharedInfo_ref.Monitor) then
otSharedInfo_ref.Monitor.Send(COmniTaskMsg_Terminated,
integer(Int64Rec(UniqueID).Lo), integer(Int64Rec(UniqueID).Hi));
otSharedInfo_ref := nil;
finally sync.Release; end;
...
end; { TOmniTask.InternalExecute }
If otSharedInfo_ref.MonitorLock.Acquire is busy waiting and the object behind otSharedInfo_ref is freed then we end up in a very nasty place. Changing the code to this stopped the AV that was happening in InternalExecute:
procedure TOmniTask.InternalExecute(calledFromTerminate: boolean);
var
...
monitorLock: TOmniCS;
...
begin
...
// with internal monitoring this will not be processed if the task controller owner is also shutting down
sync := nil; // to remove the warning in the 'finally' clause below
monitorLock := otSharedInfo_ref.MonitorLock;
monitorLock.Acquire;
try
sync := monitorLock.SyncObj;
if assigned(otSharedInfo_ref) and assigned(otSharedInfo_ref.Monitor) then
otSharedInfo_ref.Monitor.Send(COmniTaskMsg_Terminated,
integer(Int64Rec(UniqueID).Lo), integer(Int64Rec(UniqueID).Hi));
otSharedInfo_ref := nil;
finally sync.Release; end;
...
end; { TOmniTask.InternalExecute }
I did start getting AV's in the OmniTaskProcedure_Callee method then on the "task.Comm.Send(MSG_CALLEE_FINISHED)" line so it's still not fixed but this should help others/Primoz to further identify what is going on. In the new error, task.Comm is often unassigned.

Delphi. How to know if TEvent is Signaled or not?

please tell me: how to know if TEvent is Signaled or not?
Click on STOP-button = SetEvent(Events[1]);
I am trying to unzip an archive and if STOP-button is pressed then a tread must be terminated and Unzippping must be aborted.
My code:
procedure TForm2.ZipForge1OverallProgress(Sender: TObject; Progress: Double;
Operation: TZFProcessOperation; ProgressPhase: TZFProgressPhase;
var Cancel: Boolean);
begin
if Events[1]<>null then
begin
ThreadUpdating.Terminate;
Abort;
end else
form2.Update_ProgressBar.Position := Trunc(Progress);
end;
But if I press STOP-button(SetEvent(Events[1])) nothing happens.
PS: I am using WaitForMultipleObjects(Event[1],Event[2]) in a thread. Event [1] is being used as a signal of STOP in two parts: in ZipForge1OverallProgress and WaitForMultipleObjects.
Call WaitForMultipleObjects, but do it properly. You haven't shown that code, and the code you have shown doesn't look right anyway.
First, it looks like you're trying to check whether the Events[1] element is a null pointer. Null pointers in Delphi are spelled nil, not null; the latter is a function that returns a null Variant value (but since Variant is convertible to lots of other types, the compiler probably doesn't alert you that your code is wrong). Next, it looks as though the event you're handling has a Cancel parameter that you can set to notify the caller that it should stop what it's doing, but instead of just setting that, you're throwing an EAbort exception.
If the progress event you show here is really running in a separate thread, then it must not modify property of VCL objects like TProgressBar. You need to use Synchronize to make sure VCL operations only occur in the VCL thread.
As I said, you need to call WaitForMultipleObjects property. That means passing it four parameters, for one thing. You appear to have an array with at least two handles in it, so call it like this:
var
Ret: DWord;
Ret := WaitForMultipleObjects(2, #Events[1], False, Timeout);
case Ret of
Wait_Object_0: begin
// Events[1] is signaled
end;
Wait_Object_0 + 1: begin
// Events[2] is signaled
end;
Wait_Timeout: begin
// Neither is signaled. Do some more work, or go back to waiting.
end;
Wait_Failed: begin
RaiseLastOSError;
end;
end;
If all you want to do is check whether the handle is signaled, but you don't want to wait for it to become signaled if it isn't already, then use a timeout value of zero.
'if Events[1]<>null then begin' is this pseudocode? Doesn't lok like it - looks more like real Delphi to me:) If so, you are just checking to see if the Event object is assigned, rather than signaled.
If you want to poll the stop event in your OverallProgress handler, you need to call WaitForSingleObject() with a timeout of 0.
Can you not just check a 'stop' boolean in your handler? This would be much quicker than a kernel call. You may need the Event as well so that the WFMO call at the top of the thread gets signaled when an abort/terminate is needed or you might get away with signaling some other event in the WFMO array by always checking for stop:
TmyThread = class(TThread)
..
public
stopRequested:boolean;
procedure stop;
..
end;
procedure TmyThread.stop;
begin
stopRequested:=true;
someEventInWFMOarray.signal;
end;
procedure TmyThread.execute;
begin;
while true do
begin
waitForMultipeObjects();
if stopRequested then exit;
work;
end;
end;
TForm2.ZipForge1OverallProgress(sender:TObject,......)
begin
cancel:=TmyThread(Sender).stopRequested;
if cancel then exit;
doStuff;
end;

implementing a timeout when reading a file with Delphi

I have an app written in Delphi 2006 that regularly reads from a disk file located elsewhere on a network (100Mb ethernet). Occasionally the read over the network takes a very long time (like 20 secs) and the app freezes, as the read is done from an idle handler in the main thread.
OK, I could put the read operation into it's own thread, but what I would like to know is whether it is possible to specify a timeout for a file operation, so that you can give up and go and do something else, or report the fact that the read has snagged a bit earlier than 20 seconds later.
function ReadWithTimeout (var Buffer ;
N : integer ;
Timeout : integer) : boolean ;
begin
Result := false
try
SetReadTimeout (Timeout) ; // <==========================???
FileStream.Read (Buffer, N) ;
Result := true ;
except
...
end ;
end ;
Open the file for asynchronous access by including the File_Flag_Overlapped flag when you call CreateFile. Pass in a TOverlapped record when you call ReadFile, and if the read doesn't complete immediately, the function will return early. You can control how long you wait for the read to complete by calling WaitForSingleObject on the event you store in the TOverlapped structure. You can even use MsgWaitForMultipleObjects to wait; then you can be notified as soon as the read completes or a message arrives, whichever comes first, so your program doesn't need to hang at all. After you finish processing messages, you can check again whether the I/O is complete with GetOverlappedResult, resume waiting, or give up on the I/O by calling CancelIo. Make sure you read the documentation for all those functions carefully; asynchronous I/O isn't trivial.
After you've moved the read operation to a thread, you could store the value returned by timeGetTime before reading:
isReading := true;
try
startedAt := timeGetTime;
FileStream.Read (Buffer, N);
...
finally
isReading := false;
end;
and check in the idle handler if it's taken too long.
eg:
function ticksElapsed( FromTicks, ToTicks : cardinal ) : cardinal;
begin
if FromTicks < ToTicks
then Result := ToTicks - FromTicks
else Result := ( high(cardinal) - FromTicks ) + ToTicks; // There was a wraparound
end;
...
if isReading and ( ticksElapsed( startedAt, timeGetTime ) > 10 * 1000 ) // Taken too long? ~10s
then // Do something

Resources