Reputation: 13367
Can someone can explain me why i receive sometime an Exception "Argument out of range" under the ios simulator when i execute the code below? on android i never get any error. I use Delphi berlin.
the functions where the error appear :
{**********************************************************************}
procedure Twin_WorkerThreadPool.Enqueue(const Value: Twin_WorkerThread);
begin
Tmonitor.Enter(fPool);
try
fPool.Add(Value);
fSignal.SetEvent;
finally
Tmonitor.Exit(fPool);
end;
end;
{********************************************************}
function Twin_WorkerThreadPool.Dequeue: Twin_WorkerThread;
begin
Tmonitor.Enter(self); // << only one thread can execute the code below
try
Tmonitor.Enter(fPool);
try
if Fpool.Count > 0 then begin
result := fPool[Fpool.Count - 1];
fPool.Delete(Fpool.Count - 1);
exit;
end;
fSignal.ResetEvent;
finally
Tmonitor.Exit(fPool);
end;
fSignal.WaitFor(Infinite);
Tmonitor.Enter(fPool);
try
result := fPool[Fpool.Count - 1]; // << exception argument out of range ? but how it's possible ?
fPool.Delete(Fpool.Count - 1);
finally
Tmonitor.Exit(fPool);
end;
finally
Tmonitor.exit(self);
end;
end;
Below the full source code :
{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
Twin_WorkerThreadPool = class(TObject)
private
fPool: TObjectList<Twin_WorkerThread>;
fSignal: Tevent;
public
procedure Enqueue(const Value: Twin_WorkerThread);
function Dequeue: Twin_WorkerThread;
end;
{***********************************}
constructor Twin_WorkerThread.Create;
begin
FProc := nil;
FProcReadySignal := TEvent.Create(nil, false{ManualReset}, false, '');
FProcFinishedSignal := TEvent.Create(nil, false{ManualReset}, false, '');
inherited Create(False); // see http://www.gerixsoft.com/blog/delphi/fixing-symbol-resume-deprecated-warning-delphi-2010
end;
{***********************************}
destructor Twin_WorkerThread.Destroy;
begin
Terminate;
FProcReadySignal.setevent;
WaitFor;
FProcReadySignal.Free;
FProcFinishedSignal.Free;
inherited;
end;
{**********************************}
procedure Twin_WorkerThread.Execute;
begin
while True do begin
try
//wait the signal
FProcReadySignal.WaitFor(INFINITE);
//if terminated then exit
if Terminated then Break;
//execute fProc
if assigned(FProc) then FProc();
//signal the proc is finished
FProcFinishedSignal.SetEvent;
except
//hide the exception
end;
end;
end;
{**********************************************************}
procedure Twin_WorkerThread.ExecuteProc(const AProc: TProc);
begin
fProc := AProc;
FProcFinishedSignal.ResetEvent;
FProcReadySignal.SetEvent;
end;
{*****************************************************************}
procedure Twin_WorkerThread.ExecuteAndWaitProc(const AProc: TProc);
begin
fProc := AProc;
FProcFinishedSignal.ResetEvent;
FProcReadySignal.SetEvent;
FProcFinishedSignal.WaitFor(INFINITE);
end;
{********************************************************************}
constructor Twin_WorkerThreadPool.Create(const aThreadCount: integer);
var i: integer;
begin
fPool := TObjectList<Twin_WorkerThread>.create(false{aOwnObjects});
fSignal := TEvent.Create(nil, false{ManualReset}, false, '');
for I := 0 to aThreadCount - 1 do
fPool.Add(Twin_WorkerThread.Create)
end;
{***************************************}
destructor Twin_WorkerThreadPool.Destroy;
var i: integer;
begin
for I := 0 to fPool.Count - 1 do begin
fPool[i].disposeOf;
fPool[i] := nil;
end;
fPool.Free;
fSignal.Free;
inherited Destroy;
end;
{*********************************************************************}
procedure Twin_WorkerThreadPool.ExecuteAndWaitProc(const AProc: TProc);
var aThread: Twin_WorkerThread;
begin
aThread := Dequeue;
try
aThread.ExecuteAndWaitProc(aProc);
finally
Enqueue(aThread);
end;
end;
NOTE:
Just to explain a little better, remember it's only on ios it's not work, and if i add a sleep(1000) after the fSignal.resetEvent then it's work :
Tmonitor.Enter(fPool);
try
if Fpool.Count > 0 then begin
result := fPool[Fpool.Count - 1];
fPool.Delete(Fpool.Count - 1);
exit;
end;
fSignal.ResetEvent;
sleep(1000);
finally
Tmonitor.Exit(fPool);
end;
fSignal.WaitFor(Infinite);
so it's look like that the signal is not set to OFF just after doing fSignal.ResetEvent;
i m affraid it's a bug in TEvent or Tmonitor :(
Upvotes: 1
Views: 7501
Reputation: 596297
Your pool is using an auto-reset event when it should be using a manual-reset event instead. You don't want each wait operation in Dequeue()
to reset the event while there are still threads in the pool. It should be signaled while any item is in the pool, and unsignaled while the pool is empty. And your constructor is not signaling the event after adding your initial threads to the pool so they can be dequeued layer.
As for Dequeue()
itself, is a bit more complicated than it should be. It can be simplified to something more like the following instead:
procedure Twin_WorkerThreadPool.Enqueue(const Value: Twin_WorkerThread);
begin
TMonitor.Enter(fPool);
try
fPool.Add(Value);
if fPool.Count = 1 then
fSignal.SetEvent;
finally
TMonitor.Exit(fPool);
end;
end;
function Twin_WorkerThreadPool.Dequeue: Twin_WorkerThread;
begin
repeat
TMonitor.Enter(fPool);
try
if fPool.Count > 0 then begin
Result := fPool[fPool.Count - 1];
fPool.Delete(fPool.Count - 1);
if fPool.Count = 0 then
fSignal.ResetEvent;
Exit;
end;
finally
TMonitor.Exit(fPool);
end;
fSignal.WaitFor(Infinite);
until False;
end;
Something else I notice is that fPool
is a TObjectList<T>
with its OwnsObjects
property set to false, which kind of defeats the purpose of using TObjectList
. You may as well just use TList<T>
instead. And actually, the way you are using fPool
, you should use TStack<T>
instead. It would clean up your code a little more and make it that much easier to read and understand.
Try this instead:
type
Twin_WorkerThreadPool = class(TObject)
private
fPool: TStack<Twin_WorkerThread>;
fSignal: Tevent;
public
constructor Create(const aThreadCount: integer);
destructor Destroy; override;
procedure Enqueue(const Value: Twin_WorkerThread);
function Dequeue: Twin_WorkerThread;
end;
constructor Twin_WorkerThreadPool.Create(const aThreadCount: integer);
var
i: integer;
begin
inherited Create;
fPool := TStack<Twin_WorkerThread>.Create;
fSignal := TEvent.Create(nil, True{ManualReset}, False, '');
for I := 0 to aThreadCount - 1 do
fPool.Add(Twin_WorkerThread.Create);
if fPool.Count > 0 then
fPool.SetEvent;
end;
destructor Twin_WorkerThreadPool.Destroy;
var
i: integer;
begin
for I := fPool.Count - 1 downto 0 do
fPool.Pop.DisposeOf;
fPool.Free;
fSignal.Free;
inherited Destroy;
end;
procedure Twin_WorkerThreadPool.ExecuteAndWaitProc(const AProc: TProc);
var
aThread: Twin_WorkerThread;
begin
aThread := Dequeue;
try
aThread.ExecuteAndWaitProc(aProc);
finally
Enqueue(aThread);
end;
end;
procedure Twin_WorkerThreadPool.Enqueue(const Value: Twin_WorkerThread);
begin
TMonitor.Enter(fPool);
try
fPool.Push(Value);
if fPool.Count = 1 then
fSignal.SetEvent;
finally
TMonitor.Exit(fPool);
end;
end;
function Twin_WorkerThreadPool.Dequeue: Twin_WorkerThread;
begin
repeat
TMonitor.Enter(fPool);
try
if fPool.Count > 0 then begin
Result := fPool.Pop;
if fPool.Count = 0 then
fSignal.ResetEvent;
Exit;
end;
finally
TMonitor.Exit(fPool);
end;
fSignal.WaitFor(Infinite);
until False;
end;
Upvotes: 1