zeus
zeus

Reputation: 13367

Why i get an exception argument out of range?

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

Answers (1)

Remy Lebeau
Remy Lebeau

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

Related Questions