当我执行下面的代码时,有人可以解释我为什么我会在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的错误:(
答案 0 :(得分:1)
您的游泳池正在使用自动重置事件,而应该使用手动重置事件。您不希望Dequeue()
中的每个等待操作都重置事件,而池中仍有线程。它应该在任何项目在池中时发出信号,并且在池为空时无信号。并且在将初始线程添加到池中之后,构造函数不会发出事件信号,因此它们可以是出列的层。
至于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<T>
,其OwnsObjects
属性设置为false,这种方法违背了使用TObjectList
的目的。您也可以使用TList<T>
代替。实际上,您使用fPool
的方式,应该使用TStack<T>
。它会更清理你的代码并使其更容易阅读和理解。
请改为尝试:
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;