Delphi Knowledge Parallel Programming Tasks

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.

Guess you like

Origin blog.csdn.net/sensor_WU/article/details/129417572