状况
为了更好地理解PPL以及Task
如何工作,我尝试制作一个非常简单的程序,其中一旦你点击一个按钮,ListBox
就会填充一个目录列表。磁盘。
procedure TForm3.Button1Click(Sender: TObject);
var proc: ITask;
begin
//Show that something is going to happen
Button1.Caption := 'Process...';
proc := TTask.Create(
procedure
var strPath: string;
sl: TStringDynArray;
begin
if (DirectoryExists('C:\Users\albertoWinVM\Documents\uni\maths')) then
begin
ListBox1.Items.Clear;
sl := TDirectory.GetDirectories('C:\Users\albertoWinVM\Documents\uni\maths',
TSearchOption.soAllDirectories, nil);
for strPath in sl do
begin
ListBox1.Items.Add(strPath);
end;
//At the end of the task, I restore the original caption of the button
Button1.Caption := 'Go';
Label1.Caption := 'Finished';
end;
end
);
proc.Start;
end;
您可以在上面看到的文件夹maths
不是很大,任务大约需要3秒钟才能执行。任务声明如下:
type
TForm3 = class(TForm)
ListBox1: TListBox;
//... other published things var ...
private
proc: ITask;
public
//... public var ...
end;
问题
当我使用C:\Users\albertoWinVM\Documents
工作时(例如)我有很多文件夹,程序在填充ListBox之前需要3分钟。
如果我关闭程序(当任务仍在运行时)只有上面的代码,从我理解的在线阅读,任务仍然会运行,直到他还没有完成。我是对的吗?
procedure TForm3.FormDestroy(Sender: TObject);
begin
proc.Cancel;
end;
我认为我可以添加此代码以提高程序的安全性。那够了吗?
答案 0 :(得分:9)
TTask
在工作线程中运行。如图所示,您的任务代码不是线程安全的。访问UI控件时,必须与主UI线程同步。
您没有正确管理proc
变量。您有一个proc
变量声明为TForm3
类的成员,但您还在proc
方法中声明了一个本地Button1Click()
变量。该方法是将新任务分配给局部变量,永远不会分配类成员。
不,仅在Cancel()
上拨打TTask
是不够的。您的任务过程需要定期检查任务是否已被取消,以便它可以停止工作(取消TDirectory.GetDirectories()
的唯一方法是让其谓词过滤器引发异常)。
由于{em}所有目录都已找到并存储在返回的列表中,TDirectory.GetDirectories()
不会退出,如果您需要更负责任的任务和更快的UI结果,或者您只是想要为减少内存使用量,您应该在手动循环中使用FindFirst()
/ FindNext()
,然后您可以根据需要更新UI并在循环迭代之间检查取消。
话虽如此,尝试更像这样的事情:
type
TForm3 = class(TForm)
ListBox1: TListBox;
//...
private
proc: ITask;
procedure AddToListBox(batch: TStringDynArray);
procedure TaskFinished;
public
//...
end;
procedure TForm3.Button1Click(Sender: TObject);
begin
if Assigned(proc) then
begin
ShowMessage('Task is already running');
Exit;
end;
//Show that something is going to happen
Button1.Caption := 'Process...';
proc := TTask.Create(
procedure
var
strFolder: string;
sr: TSearchRec;
batch: TStringDynArray;
numInBatch: Integer;
begin
try
strFolder := 'C:\Users\albertoWinVM\Documents\uni\maths\';
if FindFirst(strFolder + '*.*', faAnyFile, sr) = 0 then
try
TThread.Queue(nil, ListBox1.Items.Clear);
batch := nil;
repeat
Form3.proc.CheckCanceled;
if (sr.Attr and faDirectory) <> 0 then
begin
if (sr.Name <> '.') and (sr.Name <> '..') then
begin
if not Assigned(batch) then
begin
SetLength(batch, 25);
numInBatch := 0;
end;
batch[numInBatch] := strFolder + sr.Name;
Inc(numInBatch);
if numInBatch = Length(batch) then
begin
TThread.Queue(nil,
procedure
begin
AddToListBox(batch);
end
end);
batch := nil;
numInBatch := 0;
end;
end;
end;
until FindNext(sr) <> 0;
finally
FindClose(sr);
end;
if numInBatch > 0 then
begin
SetLength(batch, numInBatch)
TThread.Queue(nil,
procedure
begin
AddToListBox(batch);
end
end);
end;
finally
TThread.Queue(nil, TaskFinished);
end;
end
);
proc.Start;
end;
procedure AddToListBox(batch: TStringDynArray);
begin
ListBox1.Items.AddStrings(batch);
end;
procedure TForm3.TaskFinished;
begin
proc := nil;
Button1.Caption := 'Go';
Label1.Caption := 'Finished';
end;
procedure TForm3.FormDestroy(Sender: TObject);
begin
if Assigned(proc) then
begin
proc.Cancel;
repeat
if not proc.Wait(1000) then
CheckSynchronize;
until proc = nil;
end;
end;
答案 1 :(得分:1)
您无法操纵主线程以外的线程中的UI对象。您必须同步对这些对象的访问。当多个线程试图同时对UI对象进行操作时,所有类型的意外(即:坏)事情都会发生。
例如,一旦你拥有了它,你就可以用生成的目录列表提取你想要做的工作并将它放到一个单独的方法中:
procedure TForm1.UpdateDirectoryList(AList : TStringDynArray);
var
strPath : string;
begin
ListBox1.Items.BeginUpdate;
ListBox1.Items.Clear;
for strPath in AList do ListBox1.Items.Add(strPath);
ListBox1.Items.EndUpdate;
Button1.Caption := 'Go';
Label1.Caption := 'Finished';
end;
然后,让你的任务对这个方法进行排队,让UI线程在完成长期运行的工作时执行:
procedure TForm1.Button1Click(Sender: TObject);
var proc: ITask;
begin
Button1.Caption := 'Process...';
ListBox1.Items.Clear;
proc := TTask.Create(
procedure
var
sl: TStringDynArray;
begin
if (DirectoryExists('C:\Users\albertoWinVM\Documents\uni\maths')) then
begin
sl := TDirectory.GetDirectories('C:\Users\albertoWinVM\Documents\uni\maths',
TSearchOption.soAllDirectories, nil);
TThread.Queue(nil, procedure
begin
UpdateDirectoryList(sl);
end);
end;
end);
proc.Start;
end;
这样,你的任务就只能在私有数据上运行,然后在完成后返回主线程 - 没有人在彼此的脚趾上走路。
取消线程时,仅仅拨打ITask.Cancel
是不够的 - 您必须等待它完成。理想情况下,您的任务应定期调用.CheckCanceled
,以便在外部取消时及时完成。CheckCanceled
如果任务已被取消,EOperationCancelled
将提升<<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>React JS</title>
<script src="https://unpkg.com/react@15.3.1/dist/react.min.js"></script>
<script src="https://unpkg.com/react-dom@15.3.1/dist/react-dom.min.js"></script>
<script src=" https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.24/browser.min.js" type="text/javascript" charset="utf-8" async defer></script>
</head>
<body>
<div id="ex"></div>
<script type="text/babel">
ReactDOM.render(<h1>Hello</h1>,document.getElementById('ex'));
</script>
</body>
</html>
,因此您应该尽快处理并退出。如果您按照@Remy的建议进行搜索,这会变得更加容易,因为您在每次循环迭代时都有机会检查取消。