Since Delphi XE7, the parallel programming library Parallel Programming Library ( PPL ) is supported. So what is PPL ? PPL is a part of Delphi RTL, which provides great convenience for multi-threaded (or parallel) programming.
PPL is applicable to all platforms supported by Delphi, and provides some advanced functions, such as running tasks, connecting tasks, waiting for task execution, etc. PPL is different from Thread, because PPL supports thread pools and can automatically manage CPU-based loads), developers do not need to care about and create these threads.
The use of Delphi's PPL is quite simple and does not require major changes to your application. Adding the System.Threading unit to the program can make this library.
The question is how and when can we use PPL? Well, in the usual case, you consider a task, it doesn't mean that you will no longer create threads, but, in many cases, you will use sorted tasks instead of normal threads.
Using a simple PPL, a typical code example is as follows:
procedure TMainForm.btnITaskClick(Sender: TObject);
var
LTask: ITask;
begin
LTask := TTask.Run(
procedure
var
LResult: Integer;
begin
Sleep(1000); //并行做一些事情...
LResult := Random(100); //获取执行结果 "result"
//Queue 在主进程中执行(主进程执行方法一)
TThread.Queue(nil,
procedure
begin
TaskEnd(LResult); //TaskEnd 被主 UI 调用
end);
//或者 Synchronize 在主进程中执行(主进程执行方法二)
TThread.Synchronize(TThread.Current,
procedure
begin
ShowMessage ('Hello');
end);
end);
end;
The above functions are very simple to use, but when there are many tasks running and exceptions need to be handled at the same time, things are not so simple.
At this point, we can expand the ITask interface, for example: Async.Run<T> . A full call to the Async.Run<T> method consists of three anonymous methods.
A background task : This is a function that returns some kind of data. It runs in a background thread using the PPL task.
A success callback: This is the procedure used to get the result of the background task. It runs in the main UI thread.
An error callback: This is the procedure used to catch exceptions raised by the background task, if any. It runs in the main UI thread.
The overall structure looks like this:
Async.Run<String>(
function: String
begin
//这是 "后台 "匿名方法。运行在后台线程中运行,其结果被传递到
//到 "成功 "回调过程中。
//在当前架构情况下,结果是一个字符串。
end,
procedure(const Value: String)
begin
//这是 "成功 "回调。在UI线程中运行并
//获取 "后台 "匿名方法的结果。
end,
procedure(const Ex: Exception)
begin
//这是 "错误 "回调。
//在UI线程中运行,只有在 "后台 "匿名方法产生异常时才会被调用。
//"后台 "匿名方法引发异常时才会被调用。
end)
Here is a complete AsyncTask.pas unit:
unit AsyncTask;
interface
uses
System.SysUtils,
System.Threading; //The PPL unit
type
//"后台 "任务
TAsyncBackgroundTask<T> = reference to function: T;
//"成功 "回调过程
TAsyncSuccessCallback<T> = reference to procedure(const TaskResult: T);
//"错误 "回调过程
TAsyncErrorCallback = reference to procedure(const E: Exception);
//如果用户没有提供默认的 "错误 "回调,则下面为默认的 "错误 "回调。
TAsyncDefaultErrorCallback = reference to procedure(const E: Exception;
const ExptAddress: Pointer);
//主类
Async = class sealed //一个sealed (封闭)的类不能通过继承来扩展。
public
class function Run<T>(Task: TAsyncBackgroundTask<T>;
Success: TAsyncSuccessCallback<T>;
Error : TAsyncErrorCallback = nil): ITask;
end;
var
//默认的 "错误 "回调。它是一个公共变量,以便于可以覆盖默认行为
DefaultTaskErrorHandler: TAsyncDefaultErrorCallback = nil;
implementation
uses
System.Classes;
class function Async.Run<T>(Task: TAsyncBackgroundTask<T>;
Success: TAsyncSuccessCallback<T>;
Error: TAsyncErrorCallback): ITask;
var
LRes: T;
begin
//“后台 ”任务从这里开始
Result := TTask.Run(
procedure
var
Ex: Pointer;
ExceptionAddress: Pointer;
begin
Ex := nil;
try
LRes := Task(); //运行实际的任务
if Assigned(Success) then
begin
//调用成功回调,传递结果。
TThread.Queue(nil,
procedure
begin
Success(LRes);
end);
end;
except
//让我们扩充异常对象。
Ex := AcquireExceptionObject;
ExceptionAddress := ExceptAddr;
//在主线程上排队,调用错误回调。
TThread.Queue(nil,
procedure
var
LCurrException: Exception;
begin
LCurrException := Exception(Ex);
try
if Assigned(Error) then
Error(LCurrException) //调用 "错误 "回调
else
DefaultTaskErrorHandler(LCurrException, ExceptionAddress);
finally
//释放异常对象。它是必要的,因为上面 "扩充 "了异常对象的自然寿命
//异常对象的自然寿命超过了 except 块
FreeAndNil(LCurrException);
end;
end);
end; //except
end); //task.run
end;
initialization
//这是默认的错误回调。
DefaultTaskErrorHandler :=
procedure(const E: Exception; const ExceptionAddress: Pointer)
begin
ShowException(E, ExceptionAddress);
end;
end.
Above we know how Async.Run<T> is implemented, now let us see how to use it. Open a main Form, place a
btnSimple button.
procedure TMainForm.btnSimpleClick(Sender: TObject);
begin
Async.Run<Integer>(
function: Integer //在后台长时间运行
begin
Sleep(2000);
Result := Random(100);
end,
procedure(const Value: Integer) //在用户界面中显示结果
begin
//将结果写在一个备忘录中
Log('RESULT: ' + Value.ToString);
end);
end;
In the above case, long and fake operations are performed in anonymous methods. As a function returning an integer. When that function ends, its return value is passed to another anonymous method, which is a procedure and runs in the UI thread.
thread so that it can interact with the user. If you run the program and click the button, you can verify that the UI does not freeze during long operations. (actually a call to Sleep(2000)) the UI is not frozen while running.
Placing the second button as btnWithException shows how to handle exceptions that might be thrown in a background thread.
procedure TMainForm.btnWithExceptionClick(Sender: TObject);
begin
Async.Run<String>(
function: String
begin
raise Exception.Create('This is an error message');
end,
procedure(const Value: String)
begin
// never called
end,
procedure(const Ex: Exception)
begin
Log('Exception: ' + sLineBreak + Ex.Message);
end);
end;
Pretty simple, isn't it? If something goes wrong in the background, the relevant exception object is passed to the error callback. Note that this error block
Not a standard Delphi exception block, it's just an anonymous method that actually gets an exception object.