【问题标题】:Why i get an exception argument out of range?为什么我的异常参数超出范围?
【发布时间】:2016-10-05 22:12:27
【问题描述】:

当我执行下面的代码时,有人可以解释为什么我有时会在 ios 模拟器下收到一个异常“参数超出范围”吗?在android上我从来没有得到任何错误。我用的是德尔福柏林。

出现错误的函数:

{**********************************************************************}
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;

下面是完整的源代码:

  {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  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;

注意:

只是为了解释得更好一点,记住它只在 ios 上不起作用,如果我在 fSignal.resetEvent 之后添加一个 sleep(1000) 那么它就起作用了:

    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);

所以看起来在执行 fSignal.ResetEvent 之后信号没有设置为 OFF;

我担心这是 TEvent 或 Tmonitor 中的错误 :(

【问题讨论】:

  • 在实现堆栈时不要使用入队和出队。名称应该是 Push 和 Pop。

标签: delphi firemonkey


【解决方案1】:

您的游泳池正在使用自动重置事件,而它应该使用手动重置事件。您不希望Dequeue() 中的每个等待操作在池中仍有线程时重置事件。当 any 项在池中时应发出信号,而在池为空时不发出信号。在将初始线程添加到池中之后,您的构造函数不会发出事件信号,因此它们可以出列层。

至于Dequeue() 本身,比它应该的要复杂一些。可以将其简化为类似于以下的内容:

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;

我注意到的另一件事是fPool 是一个TObjectList&lt;T&gt;,其OwnsObjects 属性设置为false,这违背了使用TObjectList 的目的。您也可以改用TList&lt;T&gt;。实际上,按照您使用fPool 的方式,您应该改用TStack&lt;T&gt;。它会使您的代码更加整洁,使其更易于阅读和理解。

试试这个:

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;

【讨论】:

  • 感谢 remy 非常好的解释。然而,这并不能解释为什么我的实现不起作用:(你的方法是有效的,但只是因为重复......直到而不是因为 fSignal.WaitFor(Infinite);......因为如果你更仔细地看到我的实现你可以看到这个错误是在设置 fSignal.ResetEvent; 然后 fSignal 仍然处于关闭状态。如果我在 fSignal.ResetEvent; 之后执行睡眠(1000),那么信号现在是 ON
  • remy,你可以看到我刚刚添加的注释,在 fSignal.ResetEvent 之后添加了睡眠;
  • @lok​​i ResetEvent应该将事件设置为关闭,SetEvent 将其设置为打开。您的Sleep() 只是让另一个线程有机会调用Enqueue() 并因此调用SetEvent()。在您首先排除您自己代码中的任何逻辑错误之前,我不会建议您向 Embarcadero 提交错误报告。而且你的代码有逻辑问题。
  • 感谢remy,ResetEvent 应该将状态设置为OFF(对不起,我在以前的cmets 中犯了一个错误,它是OFF 不是ON)。所以我做了resetEvent(将信号设置为关闭),然后等待信号设置为ON(当一个线程将在池中添加一个项目时)。通常绝对不可能有任何例外。我绝对确定这是一个错误,否则为什么它在 android/windows 上完美运行但在 ios 上崩溃?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-07-02
  • 1970-01-01
  • 1970-01-01
  • 2016-07-01
  • 2013-11-18
  • 1970-01-01
相关资源
最近更新 更多