use*_*674 5 delphi parallel-processing multithreading thread-safety wait
在柏林的Delphi 10.1中,我想添加一个可能性来停止我的问题中的响应式TParallel。&For循环。如何使TParallel。&For循环响应并将值存储在TList <T>中?。
循环计算值并将这些值存储在TList中。它与TTask.Run在单独的线程中运行以使其响应:
type
TCalculationProject=class(TObject)
private
Task: ITask;
...
public
List: TList<Real>;
...
end;
procedure TCalculationProject.CancelButtonClicked;
begin
if Assigned(Task) then
begin
Task.Cancel;
end;
end;
function TCalculationProject.CalculateListItem(const AIndex: Integer): Real;
begin
//a function which takes a lot of calculation time
//however in this example we simulate the calculation time and
//use a simple alogorithm to verify the list afterwards
Sleep(30);
Result:=10*AIndex;
end;
procedure TCalculationProject.CalculateList;
begin
List.Clear;
if Assigned(Task) then
begin
Task.Cancel;
end;
Task:=TTask.Run(
procedure
var
LoopResult: TParallel.TLoopResult;
Lock: TCriticalSection;
begin
Lock:=TCriticalSection.Create;
try
LoopResult:=TParallel.&For(0, 1000-1,
procedure(AIndex: Integer; LoopState: TParallel.TLoopState)
var
Res: Real;
begin
if (Task.Status=TTaskStatus.Canceled) and not(LoopState.Stopped) then
begin
LoopState.Stop;
end;
if LoopState.Stopped then
begin
Exit;
end;
Res:=CalculateListItem(AIndex);
Lock.Enter;
try
List.Add(Res);
finally
Lock.Leave;
end;
end
);
finally
Lock.Free;
end;
if (Task.Status=TTaskStatus.Canceled) then
begin
TThread.Synchronize(TThread.Current,
procedure
begin
List.Clear;
end
);
end
else
begin
if LoopResult.Completed then
begin
TThread.Synchronize(TThread.Current,
procedure
begin
SortList;
ShowList;
end
);
end;
end;
end
);
end;
Run Code Online (Sandbox Code Playgroud)
当前运行的计算任务应在以下情况下停止
我加了
if Assigned(Task) then
begin
Task.Cancel;
end;
Run Code Online (Sandbox Code Playgroud)
在单击“取消”按钮时的开始procedure TCalculationProject.CalculateList和procedure TCalculationProject.CancelButtonClicked调用中。
循环停止
if (Task.Status=TTaskStatus.Canceled) and not(LoopState.Stopped) then
begin
LoopState.Stop;
end;
if LoopState.Stopped then
begin
Exit;
end;
Run Code Online (Sandbox Code Playgroud)
并使用清除清单
if (Task.Status=TTaskStatus.Canceled) then
begin
TThread.Synchronize(TThread.Current,
procedure
begin
List.Clear;
end
);
end
Run Code Online (Sandbox Code Playgroud)
当我重新开始计算时,这不起作用。然后,两个计算任务正在运行。我尝试添加一个Task.Waitafter Task.Cancel来等待任务完成,然后再开始新的计算,但是没有成功。
实现这种取消/停止功能的正确的完全线程安全的正确方法是什么?
原因Wait不行,是死锁。该Synchronize调用Wait有效地阻止正在运行的任务完成。
如果将所有Synchronize调用更改为Queue,您将避免死锁。但是,与正在运行的任务Task.Cancel一起调用会引发错误,因此那里没有运气。Task.WaitEOperationCancelled
更新:这被报告为一个错误,并且在 Delphi 10.2.3 Tokyo 中仍未修复。https://quality.embarcadero.com/browse/RSP-11267
为了解决这个特定问题,您需要在Task结束时收到通知,无论是完成、取消还是停止。
当任务启动时,UI 应阻止任何启动新计算的尝试,直到前者准备好/取消。
现在,有一种安全的方法可以知道任务何时完成/停止或取消。完成后,删除方法if Assigned(Task) then Task.Cancel中的语句CalculateList。
如果该CalculateListItem方法很耗时,请考虑定期检查其中的取消状态标志。
一个例子:
type
TCalculationProject = class(TObject)
private
Task: ITask;
public
List: TList<Real>;
procedure CancelButtonClicked;
function CalculateListItem(const AIndex: Integer): Real;
procedure CalculateList(NotifyCompleted: TNotifyEvent);
Destructor Destroy; Override;
end;
procedure TCalculationProject.CancelButtonClicked;
begin
if Assigned(Task) then
begin
Task.Cancel;
end;
end;
destructor TCalculationProject.Destroy;
begin
List.Free;
inherited;
end;
function TCalculationProject.CalculateListItem(const AIndex: Integer): Real;
begin
//a function which takes a lot of calculation time
//however in this example we simulate the calculation time and
//use a simple alogorithm to verify the list afterwards
Sleep(30);
Result:=10*AIndex;
end;
procedure TCalculationProject.CalculateList(NotifyCompleted: TNotifyEvent);
begin
if not Assigned(List) then
List := TList<Real>.Create;
List.Clear;
Task:= TTask.Run(
procedure
var
LoopResult : TParallel.TLoopResult;
Lock : TCriticalSection;
begin
Lock:= TCriticalSection.Create;
try
LoopResult:= TParallel.&For(0, 1000-1,
procedure(AIndex: Integer; LoopState: TParallel.TLoopState)
var
Res: Real;
begin
if (Task.Status=TTaskStatus.Canceled) and not(LoopState.Stopped) then
begin
LoopState.Stop;
end;
if LoopState.Stopped then
begin
Exit;
end;
Res:= CalculateListItem(AIndex);
Lock.Enter;
try
List.Add(Res);
finally
Lock.Leave;
end;
end);
finally
Lock.Free;
end;
if (Task.Status = TTaskStatus.Canceled) then
TThread.Synchronize(TThread.Current,
procedure
begin
List.Clear;
end)
else
if LoopResult.Completed then
TThread.Synchronize(TThread.Current,
procedure
begin
SortList;
ShowList;
end);
// Notify the main thread that the task is ended
TThread.Synchronize(nil, // Or TThread.Queue
procedure
begin
NotifyCompleted(Self);
end);
end
);
end;
Run Code Online (Sandbox Code Playgroud)
用户界面调用:
procedure TMyForm.StartCalcClick(Sender: TObject);
begin
StartCalc.Enabled := false;
CalcObj.CalculateList(TaskCompleted);
end;
procedure TMyForm.TaskCompleted(Sender: TObject);
begin
StartCalc.Enabled := true;
end;
Run Code Online (Sandbox Code Playgroud)
在评论中,似乎用户希望在一个操作中触发取消和新任务而不被阻止。
要解决这个问题,请将标志设置为 true,然后对任务调用取消。当TaskCompleted事件被调用时,检查标志,如果设置,则启动一个新任务。使用TThread.Queue()任务来触发TaskCompleted事件。
| 归档时间: |
|
| 查看次数: |
3390 次 |
| 最近记录: |