Delphi 无法在具有对象引用的任务中执行过程
Delphi cannot execute procedure in Task with object references
我有一个简单的 class,它具有以下接口实现。
注意:TPolyBase
是一个抽象class,TPolyResult
是一个array of double
;看他们的代码不重要,这里不相关。
//INTERFACE
type
TPolynomialList = class
strict private
FPolynomialList: TObjectList<TPolyBase>;
FResult: TList<TPolyResult>;
FCanGet: boolean;
function GetResult: TList<TPolyResult>;
procedure DoSolve;
public
constructor Create(PolynomialList: TObjectList<TPolyBase>);
destructor Destroy; override;
procedure SolvePolynomials(CompletionHandler: TProc);
property Solutions: TList<TPolyResult> read GetResult;
end;
//IMPLEMENTATION
constructor TPolynomialList.Create(PolynomialList: TObjectList<TPolyBase>);
begin
FPolynomialList := PolynomialList;
FResult := TList<TPolyResult>.Create;
FCanGet := false;
end;
destructor TPolynomialList.Destroy;
begin
FResult.Free;
inherited;
end;
procedure TPolynomialList.DoSolve;
var
i: integer;
begin
for i := 0 to FPolynomialList.Count - 1 do
FResult.Add(FPolynomialList[i].GetSolutions);
FCanGet := true;
end;
function TPolynomialList.GetResult: TList<TPolyResult>;
begin
if FCanGet = false then
raise TEquationError.Create('You must solve the equation first!');
Result := FResult;
end;
procedure TPolynomialList.SolvePolynomials(CompletionHandler: TProc);
begin
TTask.Run(procedure
var
ex: TObject;
begin
try
DoSolve;
TThread.Synchronize(nil, procedure
begin
CompletionHandler;
end);
except
on E: Exception do
begin
ex := AcquireExceptionObject;
TThread.Synchronize(nil, procedure
begin
Writeln( (ex as Exception).Message );
end);
end;
end;
end);
end;
此 class 将对象列表作为输入,它有一个名为 FResult
的内部重要字段,可将结果提供给用户。仅当 SolvePolynomials 方法完成其工作时,才能从 getter 访问它。
问题出在SolvePolynomials
。我显示的代码使用了一个任务,因为对象列表的大小可能非常大,我不想冻结 UI。为什么我总是在任务代码中出现访问冲突?
请注意,以下代码工作正常,但这不是我想要的,因为如果我输入 15000,程序会冻结几秒钟。
procedure TPolynomialList.SolvePolynomials(CompletionHandler: TProc);
begin
DoSolve;
CompletionHandler;
end;
FPolynomialList
变量可能是问题所在吗?如果您查看我的 class,唯一 "taken from outside" 就是 TObjectList<TPolyBase>
,因为在构造函数中我只是简单地对引用进行赋值(我想避免复制 ok 15k 项)。所有其他变量都不与任何东西共享。
我在许多我读过的书中看到 "Delphi High Performance" 有一个调用内部 "slow" 方法的任务很好,但在这种情况下,可能会有那些参考乱七八糟某物。任何的想法?
这是我用作测试的代码:
var
a: TObjectList<TPolyBase>;
i, j: integer;
f: TPolynomialList;
s: string;
function GetRandom: integer;
begin
Result := (Random(10) + 1);
end;
begin
a := TObjectList<TPolyBase>.Create(true);
try
for i := 0 to 15000 do
begin
a.Add({*Descendant of TPolyBase*})
end;
f := TPolynomialList.Create(a);
try
f.SolvePolynomials(procedure
var
i, j: integer;
begin
for i := 0 to f.Solutions.Count - 1 do
begin
for j := Low(f.Solutions[i]) to High(f.Solutions[i]) do
Writeln({output the results...})
end;
end);
finally
f.Free;
end;
finally
a.Free;
end;
end.
您的 SolvePolynomials
方法将解决问题委托给另一个线程,并 returns 在该线程完成其任务之前。虽然该任务线程处于 运行ning 状态,但它所操作的所有数据都必须仍然存在。但是,在您的代码中,您在 SolvePolynomials
退出后立即释放必要的对象实例 - 而您的任务仍在 运行ning,因此出现错误。
您必须将这些对象的释放移动到完成处理程序中。
基本上,您的代码简化后如下所示:
type
TPolynomialList = class
public
destructor Destroy; override;
procedure DoSolve;
procedure SolvePolynomials(CompletionHandler: TProc);
end;
destructor TPolynomialList.Destroy;
begin
Writeln('Destroyed');
inherited;
end;
procedure TPolynomialList.DoSolve;
begin
Writeln('Solving');
end;
procedure TPolynomialList.SolvePolynomials(CompletionHandler: TProc);
begin
TTask.Run(
procedure
begin
try
DoSolve;
TThread.Synchronize(nil,
procedure
begin
CompletionHandler;
end);
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end);
end;
procedure Test;
var
f: TPolynomialList;
begin
f := TPolynomialList.Create;
try
f.SolvePolynomials(
procedure
begin
Writeln('Solved');
end);
finally
f.Free;
end;
end;
如果你 运行 输出将是:
Destroyed
Solving
Solved
但是,如果您将变量的释放移动到完成处理程序中,执行顺序将是正确的。
procedure Test;
var
f: TPolynomialList;
begin
f := TPolynomialList.Create;
f.SolvePolynomials(
procedure
begin
Writeln('Solved');
f.Free;
end);
end;
Solving
Solved
Destroyed
对于您的代码,这意味着将 a.Free
和 f.Free
都移动到完成处理程序中。
我有一个简单的 class,它具有以下接口实现。
注意:TPolyBase
是一个抽象class,TPolyResult
是一个array of double
;看他们的代码不重要,这里不相关。
//INTERFACE
type
TPolynomialList = class
strict private
FPolynomialList: TObjectList<TPolyBase>;
FResult: TList<TPolyResult>;
FCanGet: boolean;
function GetResult: TList<TPolyResult>;
procedure DoSolve;
public
constructor Create(PolynomialList: TObjectList<TPolyBase>);
destructor Destroy; override;
procedure SolvePolynomials(CompletionHandler: TProc);
property Solutions: TList<TPolyResult> read GetResult;
end;
//IMPLEMENTATION
constructor TPolynomialList.Create(PolynomialList: TObjectList<TPolyBase>);
begin
FPolynomialList := PolynomialList;
FResult := TList<TPolyResult>.Create;
FCanGet := false;
end;
destructor TPolynomialList.Destroy;
begin
FResult.Free;
inherited;
end;
procedure TPolynomialList.DoSolve;
var
i: integer;
begin
for i := 0 to FPolynomialList.Count - 1 do
FResult.Add(FPolynomialList[i].GetSolutions);
FCanGet := true;
end;
function TPolynomialList.GetResult: TList<TPolyResult>;
begin
if FCanGet = false then
raise TEquationError.Create('You must solve the equation first!');
Result := FResult;
end;
procedure TPolynomialList.SolvePolynomials(CompletionHandler: TProc);
begin
TTask.Run(procedure
var
ex: TObject;
begin
try
DoSolve;
TThread.Synchronize(nil, procedure
begin
CompletionHandler;
end);
except
on E: Exception do
begin
ex := AcquireExceptionObject;
TThread.Synchronize(nil, procedure
begin
Writeln( (ex as Exception).Message );
end);
end;
end;
end);
end;
此 class 将对象列表作为输入,它有一个名为 FResult
的内部重要字段,可将结果提供给用户。仅当 SolvePolynomials 方法完成其工作时,才能从 getter 访问它。
问题出在SolvePolynomials
。我显示的代码使用了一个任务,因为对象列表的大小可能非常大,我不想冻结 UI。为什么我总是在任务代码中出现访问冲突?
请注意,以下代码工作正常,但这不是我想要的,因为如果我输入 15000,程序会冻结几秒钟。
procedure TPolynomialList.SolvePolynomials(CompletionHandler: TProc);
begin
DoSolve;
CompletionHandler;
end;
FPolynomialList
变量可能是问题所在吗?如果您查看我的 class,唯一 "taken from outside" 就是 TObjectList<TPolyBase>
,因为在构造函数中我只是简单地对引用进行赋值(我想避免复制 ok 15k 项)。所有其他变量都不与任何东西共享。
我在许多我读过的书中看到 "Delphi High Performance" 有一个调用内部 "slow" 方法的任务很好,但在这种情况下,可能会有那些参考乱七八糟某物。任何的想法?
这是我用作测试的代码:
var
a: TObjectList<TPolyBase>;
i, j: integer;
f: TPolynomialList;
s: string;
function GetRandom: integer;
begin
Result := (Random(10) + 1);
end;
begin
a := TObjectList<TPolyBase>.Create(true);
try
for i := 0 to 15000 do
begin
a.Add({*Descendant of TPolyBase*})
end;
f := TPolynomialList.Create(a);
try
f.SolvePolynomials(procedure
var
i, j: integer;
begin
for i := 0 to f.Solutions.Count - 1 do
begin
for j := Low(f.Solutions[i]) to High(f.Solutions[i]) do
Writeln({output the results...})
end;
end);
finally
f.Free;
end;
finally
a.Free;
end;
end.
您的 SolvePolynomials
方法将解决问题委托给另一个线程,并 returns 在该线程完成其任务之前。虽然该任务线程处于 运行ning 状态,但它所操作的所有数据都必须仍然存在。但是,在您的代码中,您在 SolvePolynomials
退出后立即释放必要的对象实例 - 而您的任务仍在 运行ning,因此出现错误。
您必须将这些对象的释放移动到完成处理程序中。
基本上,您的代码简化后如下所示:
type
TPolynomialList = class
public
destructor Destroy; override;
procedure DoSolve;
procedure SolvePolynomials(CompletionHandler: TProc);
end;
destructor TPolynomialList.Destroy;
begin
Writeln('Destroyed');
inherited;
end;
procedure TPolynomialList.DoSolve;
begin
Writeln('Solving');
end;
procedure TPolynomialList.SolvePolynomials(CompletionHandler: TProc);
begin
TTask.Run(
procedure
begin
try
DoSolve;
TThread.Synchronize(nil,
procedure
begin
CompletionHandler;
end);
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end);
end;
procedure Test;
var
f: TPolynomialList;
begin
f := TPolynomialList.Create;
try
f.SolvePolynomials(
procedure
begin
Writeln('Solved');
end);
finally
f.Free;
end;
end;
如果你 运行 输出将是:
Destroyed
Solving
Solved
但是,如果您将变量的释放移动到完成处理程序中,执行顺序将是正确的。
procedure Test;
var
f: TPolynomialList;
begin
f := TPolynomialList.Create;
f.SolvePolynomials(
procedure
begin
Writeln('Solved');
f.Free;
end);
end;
Solving
Solved
Destroyed
对于您的代码,这意味着将 a.Free
和 f.Free
都移动到完成处理程序中。