为什么我得到异常参数超出范围? [英] Why i get an exception argument out of range?
问题描述
有人可以解释一下为什么我在执行以下代码时有时会在ios模拟器下收到异常参数超出范围的原因吗?在android上,我永远不会收到任何错误。我使用Delphi berlin。
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.
出现错误的函数:
{**********************************************************************}
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)则行得通:
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);
因此,看起来在执行fSignal.ResetEvent之后,信号并未设置为OFF;
so it's look like that the signal is not set to OFF just after doing fSignal.ResetEvent;
我认为这是TEvent或Tmonitor中的错误:(
i m affraid it's a bug in TEvent or Tmonitor :(
推荐答案
您的池使用的是自动重置事件,而应该使用的是手动重置事件。您不希望每个等待操作在 Dequeue()
可以在池中仍有线程时重置事件,当 any 项在池中时应发出信号,而在池为空时则不发出信号
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.
与 Dequeue()一样,构造函数也不会在将初始线程添加到池中后向事件发出信号,以便可以将它们出队。
本身比应该的要复杂一些,它可以简化为以下类似内容:
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;
我还注意到的是 fPool
是 TObjectList< T>
的 OwnsObjects
属性设置为false的情况,这违背了使用<$ c的目的$ c> TObjectList 。您也可以使用 TList< T>
代替。实际上,使用 fPool
的方式应该使用 TStack< T>
。
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.
尝试以下操作:
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;
这篇关于为什么我得到异常参数超出范围?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!