使用Delphi XE7并行库 [英] Using the Delphi XE7 Parallel Library
问题描述
这是单线程版本:
procedure TTerritoryList.SetUpdating(const Value:boolean);
var
i,n:整型;
begin
如果(fUpdating<> Value)或不是值然后
begin
fUpdating:= Value;
for i:= 0 to Count - 1 do
begin
Territory [i] .Updating:= Value; //<<<<< (fOnCreateShapesProgress)然后
fOnCreateShapesProgress(Self,'Reconfiguring'+ Territory [i] .Name,i /(Count-1));
结束
结束
结束
真的没有什么复杂的。如果领域列表变量被更改或设置为false,则例程将循环遍布所有销售区域并重新创建领域边界(这是耗时的任务)。
所以这是我尝试使它平行:
procedure TTerritoryList.SetUpdating(const Value:boolean);
var
i,n:整数;
begin
如果(fUpdating<> Value)或不是值然后
begin
fUpdating:= Value;
n:= Count;
i:= 0;
TParallel.For(0,Count - 1,
procedure(Index:integer)
begin
Territory [Index] .Updating:= fUpdating; //< ;<<<<时间耗费例程
TInterlocked.Increment(i);
TThread.Queue(TThread.CurrentThread,
procedure
begin
如果分配(fOnCreateShapesProgress)然后
fOnCreateShapesProgress(nil,'Reconfiguring',i / n);
end);
end
);
结束
结束
我用并行for循环替换了for-loop。柜台,'我'被锁定,因为它增加显示进度。然后我将OnCreateShapeProgress事件包装在一个TThread.Queue中,该事件将由主线程处理。 OnCreateShapeProgress事件由更新描述任务的进度条和标签的例程来处理。
如果我将OnCreateShapeProgress事件的调用排除在外,该例程将工作。它崩溃了一个EAurgumentOutOfRange错误。
所以我的问题很简单:
我做任何愚蠢的事情?
如何从TParallel.For循环或TTask中调用事件处理程序?
我可以看到的最明显的问题是你排队到工作线程。
您致电 TThread.Queue
通过 TThread.CurrentThread
。那就是你正在调用 TThread.Queue
的线程。我觉得你不应该通过 TThread.CurrentThread
到 TThread.Queue
是安全的。
而是删除该参数。使用刚接受线程程序的一个参数重载。
否则,我会注意到进度计数器 i
的增量并没有真正处理正确。那么增量是很好的,但是随后你会阅读它,那就是一场比赛。如果线程1在线程2之前递增但是线程2队列在线程1之前进行,则可以报告进度顺序。通过将计数器增量代码移动到主线程来解决这个问题。简单地增加它排队的匿名方法。增加的奖励是,您不再需要使用原子增量,因为所有修改都在主线程上。
此外,这份QC报告似乎与您所报告的相似: http://qc.embarcadero.com/wc/qcmain.aspx?d=128392
最后, AtomicIncrement
是在最新版本的Delphi中执行锁定自由递增的惯用方式。
I have a time consuming routine which I'd like to process in parallel using Delphi XE7's new parallel library.
Here is the single threaded version:
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;
There is really nothing complex going on. If the territory list variable is changed or set to false then the routine loops around all the sales territories and recreates the territory border (which is the time consuming task).
So here is my attempt to make it parallel:
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;
I've replaced the for-loop with a parallel for-loop. The counter, 'i' is locked as it is incremented to show progress. I then wrap the OnCreateShapeProgress event in a TThread.Queue, which will be handled by the main thread. The OnCreateShapeProgress event is handled by a routine which updates the progress bar and label describing the task.
The routine works if I exclude the call to the OnCreateShapeProgress event. It crashes with an EAurgumentOutOfRange error.
So my question is simple:
Am I doing anything anything stupid?
How to do you call an event handler from within a TParallel.For loop or TTask?
The most obvious problem that I can see is that you queue to the worker thread.
Your call to TThread.Queue
passes TThread.CurrentThread
. That is the very thread on which you are calling TThread.Queue
. I think it is safe to say that you should never pass TThread.CurrentThread
to TThread.Queue
.
Instead, remove that parameter. Use the one parameter overload that just accepts a thread procedure.
Otherwise I'd note that the incrementing of the progress counter i
is not really handled correctly. Well, the incrementing is fine, but you then read it later and that's a race. You can report progress out of order if thread 1 increments before thread 2 but thread 2 queues progress before thread 1. Solve that by moving the counter increment code to the main thread. Simply increment it inside the queued anonymous method. Added bonus to that is you no longer need to use an atomic increment since all modifications are on the main thread.
Beyond that, this QC report seems rather similar to what you report: http://qc.embarcadero.com/wc/qcmain.aspx?d=128392
Finally, AtomicIncrement
is the idiomatic way to perform lock free incrementing in the latest versions of Delphi.
这篇关于使用Delphi XE7并行库的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!