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.
重要注意事項:
- 在上面的示例中,我使用了
TMyDataArray
作為TypeInfo
的引數,但實際上TypeInfo(TMyDataArray)
可能不是有效的,因為TMyDataArray
在示例中並未定義。通常,您應該傳遞記錄型別本身的TypeInfo
,但TSynQueue
可能期望一個動態陣列型別來儲存其元素。然而,由於TSynQueue
的設計允許它儲存記錄的副本(而不是指標),因此您可能不需要定義一個動態陣列型別。在實際使用中,您應該查閱TSynQueue
的文件以確定如何正確地傳遞TypeInfo
。 - 記錄型別通常是透過值傳遞的,並且上面的
Push
呼叫示例假設TSynQueue
能夠處理記錄值的直接傳遞。這取決於TSynQueue
的具體實現。如果TSynQueue
被設計為儲存指向記錄的指標,那麼您可能需要定義一個動態陣列型別或使用其他機制來傳遞記錄。 - 由於
TSynQueue
是執行緒安全的,因此在多執行緒環境中使用時,您不需要擔心併發訪問問題。但是,在上面的示例中,我們為了簡化而在一個單執行緒環境中展示了其基本用法。 - 請確保將
'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
類提供了一種機制來儲存和按計劃執行一系列任務,每個任務都與一個時間戳相關聯。透過呼叫 AddTask
或 AddTasks
方法,您可以將任務新增到列表中,這些任務將在指定的延遲後執行。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.
重要注意事項:
- 單執行緒執行:上面的示例是在單執行緒環境中執行的,因此它不會按預期等待任務實際執行。在實際應用中,您應該在一個單獨的執行緒中或在事件迴圈中定期呼叫
NextPendingTask
來檢查並執行任務。 - 模擬任務執行:為了簡化示例,我們手動呼叫了
NextPendingTask
並立即列印了訊息。在實際應用中,您應該根據NextPendingTask
的返回值來決定是否執行任務,並且您可能需要等待一段時間再檢查下一個任務。 - 替換單元名稱:請確保將
'YourSynapseUnit'
替換為實際包含TSynQueue
和TPendingTaskList
類定義的單元名稱。 - 錯誤處理:示例中包含了基本的錯誤處理邏輯,但在實際應用中,您可能需要更詳細的錯誤處理和日誌記錄。
- 執行緒安全:儘管
TSynQueue
和TPendingTaskList
是執行緒安全的,但在從多個執行緒訪問它們時,您仍然需要確保正確地同步對它們的訪問(儘管在這個簡單的示例中我們沒有這樣做)。在實際應用中,您可能需要使用鎖、訊號量或其他同步機制來確保執行緒安全。然而,在這個特定的示例中,由於我們是在單執行緒環境中執行,因此不需要擔心執行緒安全問題。