mormot.core.threads--TBlockingProcess

海利鸟發表於2024-07-08

mormot.core.threads--TBlockingProcess

type
  /// TBlockingProcess 例項的當前狀態
  TBlockingEvent = (
    evNone,       // 無狀態
    evWaiting,    // 等待狀態
    evTimeOut,    // 超時狀態
    evRaised);    // 觸發狀態

  {$M+} // 開啟記憶體管理訊息,用於除錯
  /// 用於等待某個程序完成的訊號量
  // - 例如,在mormot.rest.server.pas中的TBlockingCallback使用
  // - 一旦建立,程序將透過WaitFor呼叫阻塞,當後臺執行緒呼叫NotifyFinished時釋放
  TBlockingProcess = class(TSynEvent)
  protected
    fTimeOutMs : integer;             // 超時毫秒數
    fEvent     : TBlockingEvent;      // 當前事件狀態
    fOwnedSafe : boolean;             // 是否擁有同步鎖
    fSafe      : PSynLocker;          // 同步鎖指標
    procedure ResetInternal; virtual; // 重置關聯引數(可重寫)
  public
    /// 初始化訊號量例項
    // - 指定阻塞執行應視為失敗的超時毫秒數(如果設定為0,則使用預設值3000)
    // - 應提供一個關聯的互斥鎖
    constructor Create(aTimeOutMs: integer;
                       aSafe: PSynLocker
                      );
      reintroduce; overload; virtual;
  
    /// 初始化訊號量例項
    // - 指定阻塞執行應視為失敗的超時毫秒數(如果設定為0,則使用預設值3000)
    // - 將建立並擁有一個關聯的互斥鎖
    constructor Create(aTimeOutMs: integer); reintroduce; overload; virtual;
  
    /// 銷燬例項
    destructor Destroy; override;
  
    /// 等待NotifyFinished()被呼叫或觸發超時
    // - 返回程序的最終狀態,即evRaised或evTimeOut
    function WaitFor: TBlockingEvent; reintroduce; overload; virtual;
  
    /// 等待NotifyFinished()被呼叫或指定超時時間後觸發超時
    // - 返回程序的最終狀態,即evRaised或evTimeOut
    function WaitFor(TimeOutMS: integer): TBlockingEvent; reintroduce; overload; virtual;
  
    /// 應在後臺程序完成時呼叫
    // - 呼叫者隨後將允許其WaitFor方法返回
    // - 如果成功(即狀態不是evRaised或evTimeout),則返回TRUE
    // - 如果例項已被鎖定(例如,從TBlockingProcessPool.FromCallLocked檢索時),您可以設定alreadyLocked=TRUE
    function NotifyFinished(alreadyLocked: boolean = false): boolean; virtual;
  
    /// 重置內部事件狀態為evNone的包裝器
    // - 在WaitFor/NotifyFinished過程成功後,可用於重用相同的TBlockingProcess例項
    // - 如果成功(即狀態不是evWaiting),則返回TRUE,並將當前狀態設定為evNone,並將Call屬性設定為0
    // - 如果有WaitFor當前正在進行,則返回FALSE
    function Reset: boolean; virtual;
  
    /// 圍繞fSafe^.Lock的包裝器
    procedure Lock;
  
    /// 圍繞fSafe^.Unlock的包裝器
    procedure Unlock;
  published
    /// 程序的當前狀態
    // - 在WaitFor過程後,使用Reset方法來重用此例項
    property Event: TBlockingEvent
      read fEvent;
  
    /// 建構函式中定義的超時週期(毫秒)
    property TimeOutMs: integer
      read fTimeOutMS;
  end;
  {$M-} // 關閉記憶體管理訊息

  /// 用於標識每個TBlockingProcessPool呼叫的型別
  // - 允許匹配給定的TBlockingProcessPoolItem訊號量
  TBlockingProcessPoolCall = type integer;

  /// 在TBlockingProcessPool中使用的訊號量
  // - 該訊號量具有一個Call欄位來標識每次執行
  TBlockingProcessPoolItem = class(TBlockingProcess)
  protected
    fCall: TBlockingProcessPoolCall; // 呼叫識別符號
    procedure ResetInternal; override; // 重置內部狀態
  published
    /// 當由TBlockingProcessPool擁有時的唯一識別符號
    // - Reset會將此欄位恢復為其預設值0
    property Call: TBlockingProcessPoolCall
      read fCall;
  end;

  /// TBlockingProcess的類引用型別(元類)
  TBlockingProcessPoolItemClass = class of TBlockingProcessPoolItem;

  /// 管理TBlockingProcessPoolItem例項的池
  // - 每個呼叫將透過唯一的TBlockingProcessPoolCall值進行標識
  // - 用於模擬例如從非同步事件驅動DDD程序中的阻塞執行
  // - 它還允許重用TEvent系統資源
  TBlockingProcessPool = class(TSynPersistent)
  protected
    fClass: TBlockingProcessPoolItemClass; // 池項類
    fPool: TSynObjectListLightLocked;      // 池
    fCallCounter: TBlockingProcessPoolCall; // 設定TBlockingProcessPoolItem.Call
  public
    /// 初始化池,對於給定的實現類
    constructor Create(aClass: TBlockingProcessPoolItemClass = nil); reintroduce;
  
    /// 銷燬池
    // - 還將強制所有掛起的WaitFor觸發evTimeOut
    destructor Destroy; override;
  
    /// 從內部池中預訂一個TBlockingProcess
    // - 出錯時返回nil(例如,例項正在銷燬)
    // - 或者返回與此呼叫相對應的阻塞程序例項;
    // 其Call屬性將標識非同步回撥的呼叫,然後在WaitFor之後,應執行Reset方法來釋放池的互斥鎖
    function NewProcess(aTimeOutMs: integer): TBlockingProcessPoolItem; virtual;
  
    /// 從其呼叫識別符號檢索TBlockingProcess
    // - 可用於例如從非同步程序的回撥中
    // 設定繼承自TBlockingProcess的一些附加引數,
    // 然後呼叫NotifyFinished來釋放呼叫者的WaitFor
    // - 如果leavelocked為TRUE,則返回的例項將被鎖定:呼叫者應在使用後執行result.Unlock或NotifyFinished(true)
    function FromCall(call: TBlockingProcessPoolCall;
      locked: boolean = false): TBlockingProcessPoolItem; virtual;
  end;

根據上述類定義編寫的 TBlockingProcessTBlockingProcessPool的例程程式碼。請注意,由於這些類可能依賴於特定的庫(如mORMot),以下示例將盡量保持通用性,並假設您已經有一個適當的環境來執行這些程式碼。

TBlockingProcess 例程程式碼

uses
  SysUtils, Classes, // 引入SysUtils和Classes單元以使用WriteLn和TSynLocker等
  // 假設YourSynapseUnit包含了TSynLocker和TBlockingProcess的定義
  mormot.core.os,
  mormot.core.thread;

var
  Process: TBlockingProcess;
  EventState: TBlockingEvent;
  Safe: TSynLocker;

begin
  try
    // 建立一個同步鎖
    Safe := TSynLocker.Create;
    try
      // 建立一個TBlockingProcess例項,超時設定為5000毫秒,並傳遞同步鎖
      Process := TBlockingProcess.Create(5000, @Safe);
      try
        // 模擬非同步操作,這裡我們直接呼叫WaitFor來阻塞當前執行緒
        EventState := Process.WaitFor;
        case EventState of
          evNone: WriteLn('Process state is evNone (should not happen)');
          evWaiting: WriteLn('Process state is evWaiting (should not happen during WaitFor)');
          evTimeOut: WriteLn('Process timed out');
          evRaised: WriteLn('Process was notified successfully');
        end;

        // 在實際應用中,這裡可能是另一個執行緒呼叫NotifyFinished來釋放WaitFor
        // 但為了演示,我們直接呼叫NotifyFinished(儘管這在實際應用中可能是不必要的)
        if Process.NotifyFinished then
          WriteLn('NotifyFinished called successfully (for demonstration purposes only)');

        // 重置TBlockingProcess例項以供重用(如果需要的話)
        // 在這個例子中,我們不會重用它,但展示如何呼叫Reset
        if Process.Reset then
          WriteLn('Process reset successfully');

      finally
        // 銷燬TBlockingProcess例項
        Process.Free;
      end;
    finally
      // 銷燬同步鎖
      Safe.Free;
    end;
  except
    on E: Exception do
      WriteLn('Error: ' + E.Message);
  end;
end.

注意:在上面的示例中,NotifyFinished的呼叫可能是不必要的,因為在實際應用中,它通常是由另一個執行緒在非同步操作完成時呼叫的。此外,Reset的呼叫也取決於您是否需要重用 TBlockingProcess例項。

TBlockingProcessPool 例程程式碼

uses
  SysUtils, Classes, // 引入SysUtils和Classes單元
  // 假設YourSynapseUnit包含了TBlockingProcessPool及其相關類的定義
  YourSynapseUnit;

procedure SimulateAsyncCallback(Call: TBlockingProcessPoolCall);
var
  Process: TBlockingProcessPoolItem;
begin
  // 從池中檢索與呼叫識別符號匹配的TBlockingProcessPoolItem
  Process := TBlockingProcessPool(Owner).FromCall(Call, True); // 假設Owner是TBlockingProcessPool的引用
  try
    // 在這裡模擬非同步操作的完成
    // ...

    // 通知等待的程序,非同步操作已完成
    Process.NotifyFinished(True); // 由於我們之前已經鎖定了Process,所以這裡傳遞True
  finally
    // 如果我們之前沒有呼叫NotifyFinished(True),則需要在這裡解鎖
    // 但在這個例子中,我們呼叫了NotifyFinished(True),所以不需要再次解鎖
    // Process.Unlock; // 這行是註釋掉的,因為不需要在這裡解鎖
  end;
end;

var
  Pool: TBlockingProcessPool;
  Process: TBlockingProcessPoolItem;
  Call: TBlockingProcessPoolCall;

begin
  try
    // 建立一個TBlockingProcessPool例項
    Pool := TBlockingProcessPool.Create(TBlockingProcessPoolItem);
    try
      // 從池中預訂一個新的TBlockingProcessPoolItem
      Process := Pool.NewProcess(5000);
      if Assigned(Process) then
      begin
        // 獲取呼叫識別符號(在這個例子中,我們可能不需要直接使用它,但它對於回撥是必需的)
        Call := Process.Call;

        // 在這裡,您可能會啟動一個非同步操作,並在其回撥中呼叫SimulateAsyncCallback
        // 但為了演示,我們直接呼叫SimulateAsyncCallback(儘管這在實際應用中可能是不必要的)
        SimulateAsyncCallback(Call);

        // 注意:在實際應用中,您不會在這裡立即呼叫SimulateAsyncCallback,
        // 因為非同步操作將在另一個執行緒或事件處理器中完成。
        // 在這裡,我們只是模擬了非同步回撥的行為。

        // 由於我們直接模擬了回撥,因此不需要再次呼叫WaitFor。
        // 在實際應用中,您將在啟動非同步操作後呼叫Process.WaitFor來等待其完成。

        // 重置TBlockingProcessPoolItem以供重用(如果需要的話)
        // 注意:在大多數情況下,您可能不需要在池中重用項,因為池會管理它們。
        // 但如果您確實需要重置它(例如,在異常情況下),您應該小心處理鎖定。
        // 在這個例子中,我們不會重置它,因為池會處理它。

      end;
    finally
      // 銷燬TBlockingProcessPool例項
      // 注意:在銷燬池時,所有掛起的WaitFor呼叫都將被強制超時。
      Pool.Free;
    end;
  except
    on E: Exception do
      WriteLn('Error: ' + E.Message);
  end;
end.

注意:在上面的 TBlockingProcessPool示例中,SimulateAsyncCallback過程模擬了非同步操作的回撥。然而,在實際應用中,回撥將由非同步操作本身(例如,在另一個執行緒或事件處理器中)觸發,而不是由您直接呼叫。此外,請注意 Owner的假設,它在這個例子中並未定義,但在實際應用中,您可能需要以某種方式訪問 TBlockingProcessPool的例項,以便從回撥中檢索 TBlockingProcessPoolItem。這通常透過傳遞池例項的引用或將其儲存在可從回撥訪問的全域性/靜態變數中來實現。