mormot.core.threads--TSynQueue

海利鸟發表於2024-07-08

mormot.core.threads--TSynQueue

以下是對 mormot.core.threads中部分程式碼的翻譯,特別是關於 TSynQueue類的部分:

const
  // 在這裡定義以避免在uses子句中顯式連結到syncobjs單元
  wrSignaled = syncobjs.wrSignaled; // 等待結果:已發出訊號
  wrTimeout  = syncobjs.wrTimeout;  // 等待結果:超時
  wrError    = syncobjs.wrError;    // 等待結果:錯誤

type
  // 在這裡定義以避免在uses子句中顯式連結到syncobjs單元
  // - 請注意,您可能更想使用來自mormot.core.os.pas的TSynEvent
  TWaitResult = syncobjs.TWaitResult; // 等待操作的結果型別

  // 在這裡定義以避免在uses子句中顯式連結到syncobjs單元
  // - 請注意,您可能更想使用來自mormot.core.os.pas的TSynEvent
  TEvent = syncobjs.TEvent; // 事件物件型別

{$endif PUREMORMOT2}

type
  // TThread的動態陣列型別
  TThreadDynArray = array of TThread;

  // 由本單元引發的異常類
  ESynThread = class(ESynException);

{ ************ 執行緒安全的TSynQueue和TPendingTaskList }

type
  // 執行緒安全的FIFO(先進先出)記錄佇列
  // - 內部使用TDynArray儲存,採用滑動演算法,比FPC或Delphi的TQueue或簡單的TDynArray.Add/Delete更高效
  // - 如果需要,支援TSynPersistentStore二進位制持久化
  // - 此結構也是執行緒安全的
  TSynQueue = class(TSynPersistentStore)
  protected
    // ...(省略了保護成員的詳細翻譯,它們主要是內部實現細節)
  public
    /// 初始化佇列儲存
    // - aTypeInfo應該是儲存在此TSynQueue例項中的值的動態陣列TypeInfo() RTTI指標
    // - 可以選擇性地為此例項分配一個名稱
    constructor Create(aTypeInfo: PRttiInfo; const aName: RawUtf8 = ''); reintroduce; virtual;
    /// 釋放儲存
    // - 將釋放所有內部儲存的值,並呼叫WaitPopFinalize
    destructor Destroy; override;
    /// 將一個專案推入佇列
    // - 此方法是執行緒安全的,因為它會鎖定例項
    procedure Push(const aValue);
    /// 從佇列中提取一個專案,作為FIFO(先進先出)
    // - 如果aValue已被填充為掛起的專案,則返回true,並且該專案從佇列中移除(如果不想移除,請使用Peek)
    // - 如果佇列為空,則返回false
    // - 此方法是執行緒安全的,因為它會鎖定例項
    function Pop(out aValue): boolean;
    /// 從佇列中提取一個匹配的專案,作為FIFO(先進先出)
    // - 將當前掛起的專案與aAnother值進行比較
    function PopEquals(aAnother: pointer; aCompare: TDynArraySortCompare;
      out aValue): boolean;
    /// 從佇列中查詢一個專案,作為FIFO(先進先出)
    // - 如果aValue已被填充為掛起的專案,則返回true,並且該專案不會從佇列中移除(與Pop方法不同)
    // - 如果佇列為空,則返回false
    // - 此方法是執行緒安全的,因為它會鎖定例項
    function Peek(out aValue): boolean;
    /// 等待並從佇列中提取一個專案,作為FIFO(先進先出)
    // - 如果在指定的aTimeoutMS時間內aValue已被填充為掛起的專案,則返回true
    // - 如果沒有在時間內將專案推入佇列,或者已呼叫WaitPopFinalize,則返回false
    // - aWhenIdle可用於空閒時處理訊息,例如VCL/LCL的Application.ProcessMessages
    // - 您可以選擇在返回之前比較掛起的專案(當多個執行緒將專案放入佇列時可能很有用)
    // - 此方法是執行緒安全的,但僅在需要時鎖定例項
    function WaitPop(aTimeoutMS: integer; const aWhenIdle: TThreadMethod;
      out aValue; aCompared: pointer = nil;
      aCompare: TDynArraySortCompare = nil): boolean;
    /// 在佇列中等待查詢一個專案,作為FIFO(先進先出)
    // - 在aTimeoutMS時間內返回一個指向掛起專案的指標
    // - 保持Safe.ReadWriteLock,因此呼叫者可以檢查其內容,然後如果它是預期的,則呼叫Pop(),並最終呼叫Safe.ReadWriteUnlock
    // - 如果沒有在時間內將專案推入佇列,則返回nil
    // - 此方法是執行緒安全的,但僅在需要時鎖定例項
    function WaitPeekLocked(aTimeoutMS: integer;
      const aWhenIdle: TThreadMethod): pointer;
    /// 確保任何掛起或未來的WaitPop()立即返回false
    // - 總是由Destroy解構函式呼叫
    // - 也可以從例如UI的OnClose事件中呼叫,以避免任何鎖定
    // - 此方法是執行緒安全的,但僅在需要時鎖定例項
    procedure WaitPopFinalize(aTimeoutMS: integer = 100);
    /// 刪除此佇列中當前儲存的所有專案,並清空其容量
    // - 此方法是執行緒安全的,因為它會鎖定例項
    procedure Clear;
    /// 用儲存的佇列專案初始化一個動態陣列
    // - aDynArrayValues應該是Create方法中aTypeInfo定義的變數
    // - 您可以檢索一個可選的TDynArray包裝器,例如用於二進位制或JSON持久化
    // - 此方法是執行緒安全的,並將複製佇列資料
    procedure Save(out aDynArrayValues; aDynArray: PDynArray = nil); overload;
    /// 返回當前儲存在此佇列中的專案數
    // - 此方法不是執行緒安全的,因此返回的值應是指示性的,或者您應使用顯式的Safe鎖/解鎖
    // - 如果您想檢查佇列是否為空,請呼叫Pending
    function Count: integer;
    /// 返回當前在記憶體中保留的槽位數
    // - 佇列具有最佳化的自動調整大小演算法,您可以使用此方法返回其當前容量
    // - 此方法不是執行緒安全的,因此返回的值是指示性的
    function Capacity: integer;
    /// 如果佇列中有一些專案當前掛起,則返回true
    // - 比檢查Count=0更快,並且比Pop或Peek快得多
    // - 此方法不是執行緒安全的,因此返回的值是指示性的
    function Pending: boolean;
      {$ifdef HASINLINE}inline;{$endif}
  end;

這個翻譯提供了對 TSynQueue類及其成員、方法和屬性的概述,以便更好地理解其設計目的和使用方式。請注意,翻譯過程中省略了保護成員的詳細翻譯,因為它們主要是內部實現細節,對於外部使用來說不是必需的。

在Free Pascal環境下,使用 TSynQueue類的一個示例會涉及建立佇列例項、向佇列中新增元素、從佇列中提取元素,以及處理可能的併發訪問。由於 TSynQueue是執行緒安全的,因此它非常適合在多執行緒應用程式中使用。然而,為了簡化示例,我們將在一個單執行緒環境中展示其基本用法。

請注意,由於 TSynQueue可能是特定於某個庫(如mORMot)的,因此您可能需要確保該庫已被正確安裝幷包含在您的專案中。以下是一個簡化的使用示例:

program TSynQueueExample;

{$MODE DELPHI}
{$APPTYPE CONSOLE}

uses
  SysUtils, // 包含WriteLn等標準輸出函式
  mormot.core.threads; 

type
  // 定義一個簡單的記錄型別,用於儲存在TSynQueue中
  TMyData = record
    ID: Integer;
    Value: String;
  end;

var
  Queue: TSynQueue;
  Data: TMyData;

begin
  try
    // 建立TSynQueue例項,傳遞TMyData型別的TypeInfo
    Queue := TSynQueue.Create(TypeInfo(TMyDataArray), 'MyDataQueue');
    try
      // 向佇列中新增資料
      Queue.Push(TMyData.Create(1, 'First'));
      Queue.Push(TMyData.Create(2, 'Second'));
      Queue.Push(TMyData.Create(3, 'Third'));

      // 注意:上面的Push呼叫實際上是有問題的,因為TMyData是一個記錄型別,
      // 它不是透過Create方法建立的。這裡只是為了演示如何呼叫Push。
      // 在實際使用中,您應該直接傳遞記錄的值,如下所示:
      // Queue.Push((ID: 1; Value: 'First')); // 但這取決於TSynQueue的實現是否支援記錄值傳遞

      // 由於記錄型別通常是透過值傳遞的,並且TSynQueue可能設計為儲存記錄的副本,
      // 因此您應該這樣做:
      Queue.Push((ID: 1, Value: 'First'));
      Queue.Push((ID: 2, Value: 'Second'));
      Queue.Push((ID: 3, Value: 'Third'));

      // 從佇列中提取資料(FIFO)
      while Queue.Pop(Data) do
      begin
        WriteLn('Popped Data: ID = ', Data.ID, ', Value = ', Data.Value);
      end;

      // 此時佇列應為空
      if not Queue.Pending then
        WriteLn('Queue is empty.');

    finally
      // 銷燬TSynQueue例項
      Queue.Free;
    end;
  except
    on E: Exception do
      WriteLn('Error: ', E.Message);
  end;
  WriteLn('Program ended.');
end.

重要注意事項

  1. 在上面的示例中,我使用了 TMyDataArray作為 TypeInfo的引數,但實際上 TypeInfo(TMyDataArray)可能不是有效的,因為 TMyDataArray在示例中並未定義。通常,您應該傳遞記錄型別本身的 TypeInfo,但 TSynQueue可能期望一個動態陣列型別來儲存其元素。然而,由於 TSynQueue的設計允許它儲存記錄的副本(而不是指標),因此您可能不需要定義一個動態陣列型別。在實際使用中,您應該查閱 TSynQueue的文件以確定如何正確地傳遞 TypeInfo
  2. 記錄型別通常是透過值傳遞的,並且上面的 Push呼叫示例假設 TSynQueue能夠處理記錄值的直接傳遞。這取決於 TSynQueue的具體實現。如果 TSynQueue被設計為儲存指向記錄的指標,那麼您可能需要定義一個動態陣列型別或使用其他機制來傳遞記錄。
  3. 由於 TSynQueue是執行緒安全的,因此在多執行緒環境中使用時,您不需要擔心併發訪問問題。但是,在上面的示例中,我們為了簡化而在一個單執行緒環境中展示了其基本用法。
  4. 請確保將 'YourSynapseUnit'替換為實際包含 TSynQueue定義的單元名稱。如果 TSynQueue是mORMot庫的一部分,那麼您可能需要包含mORMot的相應單元。

以下是對 TPendingTaskList及其相關型別的翻譯,包括其保護型別、建構函式、方法和屬性:

type
  /// 內部項定義,用於TPendingTaskList儲存
  // 該記錄定義了待執行任務的時間戳和任務內容(以RawByteString形式儲存)
  TPendingTaskListItem = packed record
    /// 當TPendingTaskList.GetTimestamp達到此值時,應執行該任務
    Timestamp: Int64;
    /// 與此時間戳相關聯的任務,以原始二進位制字串形式儲存
    Task: RawByteString;
  end;

  /// 內部列表定義,用於TPendingTaskList儲存
  // TPendingTaskListItem的動態陣列
  TPendingTaskListItemDynArray = array of TPendingTaskListItem;

  /// 執行緒安全的任務列表,任務以RawByteString形式儲存,並帶有時間戳
  // - 您可以向內部列表新增任務,在給定延遲後執行,使用類似釋出/檢視的演算法
  // - 執行延遲可能不準確,但會根據每次呼叫NextPendingTask和GetTimestamp的解析度進行最佳猜測
  TPendingTaskList = class
  protected
    // 內部儲存結構和同步訪問
    fTask: TPendingTaskListItemDynArray; // 儲存待執行任務的陣列
    fTasks: TDynArrayLocked; // 對fTask陣列的封裝,提供執行緒安全的訪問
    // 獲取當前儲存的任務數量(執行緒安全)
    function GetCount: integer;
    // 獲取當前時間戳(預設為GetTickCount64)
    function GetTimestamp: Int64; virtual;
  public
    // 初始化列表的記憶體和資源
    constructor Create; reintroduce;
    // 新增一個任務,指定從當前時間開始的延遲(毫秒)
    procedure AddTask(aMilliSecondsDelayFromNow: integer;
      const aTask: RawByteString); virtual;
    // 新增多個任務,指定任務之間的延遲(毫秒)
    // - 第一個提供的延遲將從當前時間開始計算,然後指定下一個提供的任務之間的等待時間
    // - 也就是說,aMilliSecondsDelays不是絕對延遲
    procedure AddTasks(const aMilliSecondsDelays: array of integer;
      const aTasks: array of RawByteString);
    // 檢索下一個待執行的任務
    // - 如果沒有在當前時間可用的計劃任務,則返回''
    // - 根據指定的延遲返回下一個任務
    function NextPendingTask: RawByteString; virtual;
    // 清空所有待執行的任務
    procedure Clear; virtual;
    // 訪問內部儲存的TPendingTaskListItem.Timestamp值
    // - 對應當前時間
    // - 預設實現返回GetTickCount64,在Windows下典型解析度為16毫秒
    property Timestamp: Int64 read GetTimestamp;
    // 當前定義了多少個待執行任務
    property Count: integer read GetCount;
    // 對內部任務列表的直接低階訪問
    // - 警告:此動態陣列的長度是列表的容量:請使用Count屬性來檢索儲存的項的確切數量
    // - 使用Safe.Lock/TryLock與try ... finally Safe.Unlock塊進行執行緒安全的訪問
    // - 項按時間戳遞增儲存,即第一項是NextPendingTask方法將返回的下一個項
    property Task: TPendingTaskListItemDynArray read fTask;
  end;

TPendingTaskList類提供了一種機制來儲存和按計劃執行一系列任務,每個任務都與一個時間戳相關聯。透過呼叫 AddTaskAddTasks方法,您可以將任務新增到列表中,這些任務將在指定的延遲後執行。NextPendingTask方法用於檢索下一個待執行的任務,而 Clear方法用於清空整個任務列表。

注意,TPendingTaskList類中的 Timestamp屬性和 GetTimestamp方法是用於確定何時執行任務的關鍵。GetTimestamp方法預設返回 GetTickCount64的值,但在子類中可以根據需要進行重寫,以提供不同的時間戳生成邏輯。同樣,NextPendingTask方法也是虛擬的,允許在子類中實現自定義的任務檢索邏輯。

在Free Pascal環境下,結合 TPendingTaskList類的定義,我們可以編寫一個示例程式來展示這兩個類的基本用法。以下是一個簡化的示例,一個 TPendingTaskList例項來按計劃執行任務(在這個例子中,任務只是簡單地列印訊息)。

請注意,由於 TPendingTaskList可能是特定於某個庫(如mORMot)的,因此您需要確保該庫已被正確安裝幷包含在您的專案中。此外,為了簡化示例,我們將在一個單執行緒環境中執行它,儘管這些類設計用於多執行緒環境。

program PendingTaskListExample;

{$MODE DELPHI}
{$APPTYPE CONSOLE}

uses
  SysUtils, // 包含WriteLn等標準輸出函式
  YourSynapseUnit; // 替換為實際包含這些類定義的單元名稱

var
  Queue: TSynQueue;
  TaskList: TPendingTaskList;
  I: Integer;
  TaskMessage: RawByteString;

begin
  try
    // 建立TPendingTaskList例項來按計劃執行任務
    TaskList := TPendingTaskList.Create;
    try
      // 新增一些計劃任務到列表中
      // 假設每個任務只是列印一條訊息,延遲從當前時間開始計算
      TaskList.AddTask(1000, 'Task 1 in 1 second'); // 1秒後執行
      TaskList.AddTask(2000, 'Task 2 in 2 seconds'); // 2秒後執行

      // 注意:由於這個示例是在單執行緒環境中執行的,
      // 我們不會等待任務實際執行。在實際應用中,
      // 您可能需要在另一個執行緒中呼叫NextPendingTask,
      // 或者使用某種形式的定時器或事件迴圈來檢查並執行任務。

      // 為了模擬任務執行,我們可以手動呼叫NextPendingTask
      // 並列印訊息(但在實際應用中,這通常不是您想要的方式)
      repeat
        TaskMessage := TaskList.NextPendingTask;
        if TaskMessage <> '' then
          WriteLn('Executing Task: ', TaskMessage)
        else
          Break; // 沒有更多待執行的任務,退出迴圈

        // 在這裡,我們實際上應該等待一段時間再檢查下一個任務,
        // 但為了簡化示例,我們只是立即再次檢查(這不是實際用法)
      until False;

    finally
      // 銷燬TPendingTaskList例項(在這個簡單的示例中可能不是必需的,
      // 但為了完整性而包含)
      TaskList.Free;
    end;

  except
    on E: Exception do
      WriteLn('Error: ', E.Message);
  end;
  WriteLn('Program ended.');
end.

重要注意事項

  1. 單執行緒執行:上面的示例是在單執行緒環境中執行的,因此它不會按預期等待任務實際執行。在實際應用中,您應該在一個單獨的執行緒中或在事件迴圈中定期呼叫 NextPendingTask來檢查並執行任務。
  2. 模擬任務執行:為了簡化示例,我們手動呼叫了 NextPendingTask並立即列印了訊息。在實際應用中,您應該根據 NextPendingTask的返回值來決定是否執行任務,並且您可能需要等待一段時間再檢查下一個任務。
  3. 替換單元名稱:請確保將 'YourSynapseUnit'替換為實際包含 TSynQueueTPendingTaskList類定義的單元名稱。
  4. 錯誤處理:示例中包含了基本的錯誤處理邏輯,但在實際應用中,您可能需要更詳細的錯誤處理和日誌記錄。
  5. 執行緒安全:儘管 TSynQueueTPendingTaskList是執行緒安全的,但在從多個執行緒訪問它們時,您仍然需要確保正確地同步對它們的訪問(儘管在這個簡單的示例中我們沒有這樣做)。在實際應用中,您可能需要使用鎖、訊號量或其他同步機制來確保執行緒安全。然而,在這個特定的示例中,由於我們是在單執行緒環境中執行,因此不需要擔心執行緒安全問題。