我有一个耗时的例程,我想使用Delphi XE7的新并行库并行处理。
以下是单线程版本:
procedure TTerritoryList.SetUpdating(const Value: boolean);
var
i, n: Integer;
begin
if (fUpdating <> Value) or not Value then
begin
fUpdating := Value;
for i := 0 to Count - 1 do
begin
Territory[i].Updating := Value; // <<<<<< Time consuming routine
if assigned(fOnCreateShapesProgress) then
fOnCreateShapesProgress(Self, 'Reconfiguring ' + Territory[i].Name, i / (Count - 1));
end;
end;
end;
实际上并没有什么复杂的事情发生。如果区域列表变量已更改或设置为false,则例程将循环遍历所有销售区域并重新创建区域边界(这是一项耗时的任务)。
所以这是我试图让它并行:
procedure TTerritoryList.SetUpdating(const Value: boolean);
var
i, n: Integer;
begin
if (fUpdating <> Value) or not Value then
begin
fUpdating := Value;
n := Count;
i := 0;
TParallel.For(0, Count - 1,
procedure(Index: integer)
begin
Territory[Index].Updating := fUpdating; // <<<<<< Time consuming routine
TInterlocked.Increment(i);
TThread.Queue(TThread.CurrentThread,
procedure
begin
if assigned(fOnCreateShapesProgress) then
fOnCreateShapesProgress(nil, 'Reconfiguring ', i / n);
end);
end
);
end;
end;
我用并行for循环替换了for循环。计数器'i'被锁定,因为它增加以显示进度。然后我将OnCreateShapeProgress事件包装在TThread.Queue中,该事件将由主线程处理。 OnCreateShapeProgress事件由一个例程处理,该例程更新描述任务的进度条和标签。
如果我排除了对OnCreateShapeProgress事件的调用,则例程有效。它因EAurgumentOutOfRange错误而崩溃。
所以我的问题很简单:
我做任何蠢事吗?
如何从TParallel.For循环或TTask中调用事件处理程序?
答案 0 :(得分:4)
我能看到的最明显的问题是你排队到工作线程。
您对TThread.Queue
的来电通过TThread.CurrentThread
。这就是你打电话给TThread.Queue
的主题。我认为你绝不应该将TThread.CurrentThread
传递给TThread.Queue
,这是安全的。
相反,删除该参数。使用只接受线程过程的one参数重载。
否则我会注意到进度计数器i
的递增并未真正正确处理。好吧,增量很好,但是你稍后再读它就是一场比赛。如果线程1在线程2之前递增但线程2在线程1之前排队进度,则可以无序地报告进度。通过将计数器增量代码移动到主线程来解决此问题。只需在排队的匿名方法中增加它。额外的奖励是你不再需要使用原子增量,因为所有修改都在主线程上。
除此之外,此质量控制报告似乎与您报告的内容非常相似:http://qc.embarcadero.com/wc/qcmain.aspx?d=128392
最后,AtomicIncrement
是在最新版本的Delphi中执行无锁递增的惯用方法。