找回密码
 立即注册
首页 资源区 代码 mormot.core.threads--TSynQueue

mormot.core.threads--TSynQueue

伯绮梦 3 天前
mormot.core.threads--TSynQueue

以下是对 mormot.core.threads中部分代码的翻译,特别是关于 TSynQueue类的部分:
  1. const
  2.   // 在这里定义以避免在uses子句中显式链接到syncobjs单元
  3.   wrSignaled = syncobjs.wrSignaled; // 等待结果:已发出信号
  4.   wrTimeout  = syncobjs.wrTimeout;  // 等待结果:超时
  5.   wrError    = syncobjs.wrError;    // 等待结果:错误
  6. type
  7.   // 在这里定义以避免在uses子句中显式链接到syncobjs单元
  8.   // - 请注意,您可能更想使用来自mormot.core.os.pas的TSynEvent
  9.   TWaitResult = syncobjs.TWaitResult; // 等待操作的结果类型
  10.   // 在这里定义以避免在uses子句中显式链接到syncobjs单元
  11.   // - 请注意,您可能更想使用来自mormot.core.os.pas的TSynEvent
  12.   TEvent = syncobjs.TEvent; // 事件对象类型
  13. {$endif PUREMORMOT2}
  14. type
  15.   // TThread的动态数组类型
  16.   TThreadDynArray = array of TThread;
  17.   // 由本单元引发的异常类
  18.   ESynThread = class(ESynException);
  19. { ************ 线程安全的TSynQueue和TPendingTaskList }
  20. type
  21. /// 线程安全的 FIFO(先进先出)顺序记录队列
  22. // - 内部使用 TDynArray 存储,配合滑动算法,比 FPC 或 Delphi 的 TQueue 或简单的 TDynArray.Add/Delete 更高效
  23. // - 如需,支持 TSynPersistentStore 二进制持久化
  24. // - 该结构在设计上也是线程安全的
  25. TSynQueue = class(TSynPersistentStore)
  26. protected
  27.   fValues: TDynArray;
  28.   fValueVar: pointer;
  29.   fCount, fFirst, fLast: integer;
  30.   fWaitPopFlags: set of (wpfDestroying);
  31.   fWaitPopCounter: integer;
  32.   
  33.   /// 获取队列中的项目数量
  34.   function GetCount: integer;
  35.   
  36.   procedure InternalPop(aValue: pointer);
  37.   procedure InternalGrow;
  38.   function InternalDestroying(incPopCounter: integer): boolean;
  39.   function InternalWaitDone(starttix, endtix: Int64; const idle: TThreadMethod): boolean;
  40.   
  41.   /// 实现持久化的低级 TSynPersistentStore 方法
  42.   procedure LoadFromReader; override;
  43.   procedure SaveToWriter(aWriter: TBufferWriter); override;
  44. public
  45.   /// 初始化队列存储
  46.   // - aTypeInfo 应是动态数组的 TypeInfo() RTTI 指针,用于在此 TSynQueue 实例中存储值
  47.   // - 可选地为该实例分配一个名称
  48.   constructor Create(aTypeInfo: PRttiInfo; const aName: RawUtf8 = ''); reintroduce; virtual;
  49.   
  50.   /// 释放存储
  51.   // - 释放所有内部存储的值,并调用 WaitPopFinalize
  52.   destructor Destroy; override;
  53.   
  54.   /// 将一个项目推入队列
  55.   // - 由于会锁定实例,因此此方法是线程安全的
  56.   procedure Push(const aValue);
  57.   
  58.   /// 从队列中按 FIFO(先进先出)顺序提取一个项目
  59.   // - 如果 aValue 被填充了一个待处理项目,则返回 true,并且该项目从队列中移除(如果不想移除,请使用 Peek)
  60.   // - 如果队列为空,则返回 false
  61.   // - 由于会锁定实例,因此此方法是线程安全的
  62.   function Pop(out aValue): boolean;
  63.   
  64.   /// 从队列中按 FIFO(先进先出)顺序提取一个匹配的项目
  65.   // - 将当前待处理项目与 aAnother 值进行比较
  66.   function PopEquals(aAnother: pointer; aCompare: TDynArraySortCompare;
  67.     out aValue): boolean;
  68.   
  69.   /// 从队列中按 FIFO(先进先出)顺序查找一个项目
  70.   // - 如果 aValue 被填充了一个待处理项目(不从队列中移除,如 Pop 方法所做的那样),则返回 true
  71.   // - 如果队列为空,则返回 false
  72.   // - 由于会锁定实例,因此此方法是线程安全的
  73.   function Peek(out aValue): boolean;
  74.   
  75.   /// 等待并从队列中按 FIFO(先进先出)顺序提取一个项目
  76.   // - 如果在指定的 aTimeoutMS 时间内 aValue 被填充了一个待处理项目,则返回 true
  77.   // - 如果没有在时间内将任何内容推入队列,或者调用了 WaitPopFinalize,则返回 false
  78.   // - aWhenIdle 可以被分配,例如用于 VCL/LCL Application.ProcessMessages
  79.   // - 可以在返回之前选择性地比较待处理项目(例如,当多个线程正在向队列中放入项目时)
  80.   // - 此方法是线程安全的,但仅在必要时锁定实例
  81.   function WaitPop(
  82.        aTimeoutMS     : integer;
  83.        const aWhenIdle: TThreadMethod;
  84.        out aValue;
  85.        aCompared      : pointer = nil;
  86.        aCompare       : TDynArraySortCompare = nil
  87.                   ): boolean;
  88.   
  89.   /// 等待并从队列中按 FIFO(先进先出)顺序查找一个项目
  90.   // - 在 aTimeoutMS 时间内返回一个指向待处理项目的指针
  91.   // - 保持 Safe.ReadWriteLock,因此调用者可以检查其内容,如果它是预期的,则调用 Pop(),并最终调用 Safe.ReadWriteUnlock
  92.   // - 如果在时间内没有将任何内容推入队列,则返回 nil
  93.   // - 此方法是线程安全的,但仅在必要时锁定实例
  94.   function WaitPeekLocked(
  95.        aTimeoutMS: integer;
  96.        const aWhenIdle: TThreadMethod
  97.        ): pointer;
  98.   
  99.   /// 确保任何挂起或未来的 WaitPop() 立即返回 false
  100.   // - 始终由 Destroy 析构函数调用
  101.   // - 也可以从例如 UI 的 OnClose 事件中调用,以避免任何锁定
  102.   // - 此方法是线程安全的,但仅在必要时锁定实例
  103.   procedure WaitPopFinalize(aTimeoutMS: integer = 100);
  104.   
  105.   /// 删除此队列中当前存储的所有项目,并清空其容量
  106.   // - 由于会锁定实例,因此此方法是线程安全的
  107.   procedure Clear;
  108.   
  109.   /// 使用存储的队列项目初始化动态数组
  110.   // - aDynArrayValues 应是定义为从 Create 传递的 aTypeInfo 的变量
  111.   // - 您可以检索一个可选的 TDynArray 包装器,例如用于二进制或 JSON 持久化
  112.   // - 此方法是线程安全的,并将复制队列数据
  113.   procedure Save(out aDynArrayValues; aDynArray: PDynArray = nil); overload;
  114.   
  115.   /// 返回当前存储在此队列中的项目数量
  116.   // - 此方法不是线程安全的,因此返回的值仅具有指示性,或者您应该使用显式的 Safe lock/unlock
  117.   // - 如果您想检查队列是否为空,请调用 Pending
  118.   function Count: integer;
  119.   
  120.   /// 返回当前在内存中预留的槽位数
  121.   // - 队列具有优化的自动调整大小算法,您可以使用此方法返回其当前容量
  122.   // - 此方法不是线程安全的,因此返回的值仅具有指示性
  123.   function Capacity: integer;
  124.   
  125.   /// 如果队列中当前有待处理项目,则返回 true
  126.   // - 比检查 Count=0 更快,并且比 Pop 或 Peek 快得多
  127.   // - 此方法不是线程安全的,因此返回的值仅具有指示性
  128.   function Pending: boolean;
  129.     {$ifdef HASINLINE}inline;{$endif}
  130. end;
复制代码
在这个修正后的代码中,每个 ///注释前都多留了一个空行,以提高代码的可读性和整洁度。
这个翻译提供了对 TSynQueue类及其成员、方法和属性的概述,以便更好地理解其设计目的和使用方式。请注意,翻译过程中省略了保护成员的详细翻译,因为它们主要是内部实现细节,对于外部使用来说不是必需的。
在Free Pascal环境下,使用 TSynQueue类的一个示例会涉及创建队列实例、向队列中添加元素、从队列中提取元素,以及处理可能的并发访问。由于 TSynQueue是线程安全的,因此它非常适合在多线程应用程序中使用。然而,为了简化示例,我们将在一个单线程环境中展示其基本用法。
请注意,由于 TSynQueue可能是特定于某个库(如mORMot)的,因此您可能需要确保该库已被正确安装并包含在您的项目中。以下是一个简化的使用示例:
  1. ;program TSynQueueExample;
  2. {$MODE DELPHI}
  3. {$APPTYPE CONSOLE}
  4. uses
  5.   SysUtils, // 包含WriteLn等标准输出函数
  6.   mormot.core.threads;
  7. type
  8.   // 定义一个简单的记录类型,用于存储在TSynQueue中
  9.   TMyData = record
  10.     ID: Integer;
  11.     Value: String;
  12.   end;
  13. TMyDataArray= array of TMyData ; // 容器类型声明
  14. const
  15.   MyRecords: array[1..3] of TMyData = (
  16.     (ID: 10; Value: 'Record 1'),
  17.     (ID: 20; Value: 'Record 2'),
  18.     (ID: 30; Value: 'Record 3')
  19.   );
  20. var
  21.   Queue: TSynQueue;
  22.   Data: TMyData;
  23. begin
  24.   try
  25.     // 创建TSynQueue实例,传递TMyData类型的TypeInfo
  26.     Queue := TSynQueue.Create(TypeInfo(TMyDataArray), 'MyDataQueue');
  27.     try
  28.       // 向队列中添加数据
  29.       Queue.Push(TMyData.Create(1, 'First'));
  30.       Queue.Push(TMyData.Create(2, 'Second'));
  31.       Queue.Push(TMyData.Create(3, 'Third'));
  32.       // 注意:上面的Push调用实际上是有问题的,因为TMyData是一个记录类型,
  33.       // 它不是通过Create方法创建的。这里只是为了演示如何调用Push。
  34.       // 在实际使用中,您应该直接传递记录的值,如下所示:
  35.       // Queue.Push((ID: 1; Value: 'First')); // 但这取决于TSynQueue的实现是否支持记录值传递
  36.       // 由于记录类型通常是通过值传递的,并且TSynQueue可能设计为存储记录的副本,
  37.       // 因此您应该这样做:但要注意pascal不可以直接写记录型常量,要先定义。
  38.       Queue.Push((ID: 1; Value: 'First'));
  39.       Queue.Push((ID: 2; Value: 'Second'));
  40.       Queue.Push((ID: 3; Value: 'Third'));
  41.   
  42.       /// 以上是思路,下面是可行代码
  43.       Queue.Push(MyRecords[1]);
  44.       Queue.Push(Data);
  45.       Queue.Push(MyRecords[2]);
  46.       Queue.Push(MyRecords[3]);   
  47.       // 从队列中提取数据(FIFO)
  48.       while Queue.Pop(Data) do
  49.       begin
  50.         WriteLn('Popped Data: ID = ', Data.ID, ', Value = ', Data.Value);
  51.       end;
  52.       // 此时队列应为空
  53.       if not Queue.Pending then
  54.         WriteLn('Queue is empty.');
  55.     finally
  56.       // 销毁TSynQueue实例
  57.       Queue.Free;
  58.     end;
  59.   except
  60.     on E: Exception do
  61.       WriteLn('Error: ', E.Message);
  62.   end;
  63.   WriteLn('Program ended.');
  64. end.
复制代码
重要注意事项

  • 在上面的示例中,我使用了 TMyDataArray作为 TypeInfo的参数,因为 TMyDataArray 这里是表示要声明一个动态数组作为容器。
mormot.core.threads--TPendingTaskList
  1. type
  2.   /// 内部项定义,用于TPendingTaskList存储
  3.   // 该记录定义了待执行任务的时间戳和任务内容(以RawByteString形式存储)
  4.   TPendingTaskListItem = packed record
  5.     /// 当TPendingTaskList.GetTimestamp达到此值时,应执行该任务
  6.     Timestamp: Int64;
  7.     /// 与此时间戳相关联的任务,以原始二进制字符串形式存储
  8.     Task: RawByteString;
  9.   end;
  10.   /// 内部列表定义,用于TPendingTaskList存储
  11.   // TPendingTaskListItem的动态数组
  12.   TPendingTaskListItemDynArray = array of TPendingTaskListItem;
  13.   /// 线程安全的任务列表,以RawByteString形式存储,并带有时间戳  
  14.   // - 您可以向内部列表添加任务,使用类似发布/查看的算法,在给定延迟后执行  
  15.   // - 执行延迟不保证精确,而是根据每次NextPendingTask调用和GetTimestamp分辨率的最佳猜测  
  16.   TPendingTaskList = class  
  17.   protected  
  18.     fTask: TPendingTaskListItemDynArray;  
  19.     fTasks: TDynArrayLocked;  
  20.     function GetCount: integer;  
  21.     function GetTimestamp: Int64; virtual; // 默认返回GetTickCount64  
  22.   public  
  23.     /// 初始化列表内存和资源  
  24.     constructor Create; reintroduce;  
  25.   
  26.     /// 添加一个任务,指定从当前时间开始的延迟(毫秒)  
  27.     procedure AddTask(aMilliSecondsDelayFromNow: integer;  
  28.       const aTask: RawByteString); virtual;  
  29.   
  30.     /// 添加多个任务,指定任务之间的延迟(毫秒)  
  31.     // - 第一个提供的延迟将从当前时间开始计算,然后指定等待下一个提供任务之间需要多少时间  
  32.     // - 即,aMilliSecondsDelays不是绝对延迟  
  33.     procedure AddTasks(const aMilliSecondsDelays: array of integer;  
  34.       const aTasks: array of RawByteString);  
  35.   
  36.     /// 检索下一个待处理任务  
  37.     // - 如果没有当前时间可用的计划任务,则返回''  
  38.     // - 根据指定的延迟定义返回下一个堆栈  
  39.     function NextPendingTask: RawByteString; virtual;  
  40.   
  41.     /// 清空所有待处理任务  
  42.     procedure Clear; virtual;  
  43.   
  44.     /// 访问内部TPendingTaskListItem.Timestamp存储的值  
  45.     // - 对应当前时间  
  46.     // - 默认实现是返回GetTickCount64,在Windows下典型分辨率为16毫秒  
  47.     property Timestamp: Int64  
  48.       read GetTimestamp;  
  49.   
  50.     /// 当前定义了多少个待处理任务  
  51.     property Count: integer  
  52.       read GetCount;  
  53.   
  54.     /// 直接低级访问内部任务列表  
  55.     // - 警告:此动态数组长度是列表容量:使用Count属性来检索存储的精确项目数  
  56.     // - 使用Safe.Lock/TryLock与try ... finally Safe.Unlock块进行线程安全的访问此数组  
  57.     // - 项目按递增的Timestamp存储,即第一个项目是NextPendingTask方法将返回的下一个项目  
  58.     property Task: TPendingTaskListItemDynArray  
  59.       read fTask;  
  60.   end;
复制代码
TPendingTaskList类提供了一种机制来存储和按计划执行一系列任务,每个任务都与一个时间戳相关联。通过调用 AddTask或 AddTasks方法,您可以将任务添加到列表中,这些任务将在指定的延迟后执行。NextPendingTask方法用于检索下一个待执行的任务,而 Clear方法用于清空整个任务列表。
注意,TPendingTaskList类中的 Timestamp属性和 GetTimestamp方法是用于确定何时执行任务的关键。GetTimestamp方法默认返回 GetTickCount64的值,但在子类中可以根据需要进行重写,以提供不同的时间戳生成逻辑。同样,NextPendingTask方法也是虚拟的,允许在子类中实现自定义的任务检索逻辑。
在Free Pascal环境下,结合  TPendingTaskList类的定义,我们可以编写一个示例程序来展示这两个类的基本用法。以下是一个简化的示例,一个 TPendingTaskList实例来按计划执行任务(在这个例子中,任务只是简单地打印消息)。
请注意,由于 TPendingTaskList可能是特定于某个库(如mORMot)的,因此您需要确保该库已被正确安装并包含在您的项目中。此外,为了简化示例,我们将在一个单线程环境中运行它,尽管这些类设计用于多线程环境。
  1. program PendingTaskListExample;
  2. {$MODE DELPHI}
  3. {$APPTYPE CONSOLE}
  4. uses
  5.   SysUtils, // 包含WriteLn等标准输出函数
  6.   YourSynapseUnit; // 替换为实际包含这些类定义的单元名称
  7. var
  8.   Queue: TSynQueue;
  9.   TaskList: TPendingTaskList;
  10.   I: Integer;
  11.   TaskMessage: RawByteString;
  12. begin
  13.   try
  14.     // 创建TPendingTaskList实例来按计划执行任务
  15.     TaskList := TPendingTaskList.Create;
  16.     try
  17.       // 添加一些计划任务到列表中
  18.       // 假设每个任务只是打印一条消息,延迟从当前时间开始计算
  19.       TaskList.AddTask(1000, 'Task 1 in 1 second'); // 1秒后执行
  20.       TaskList.AddTask(2000, 'Task 2 in 2 seconds'); // 2秒后执行
  21.       // 注意:由于这个示例是在单线程环境中运行的,
  22.       // 我们不会等待任务实际执行。在实际应用中,
  23.       // 您可能需要在另一个线程中调用NextPendingTask,
  24.       // 或者使用某种形式的定时器或事件循环来检查并执行任务。
  25.       // 为了模拟任务执行,我们可以手动调用NextPendingTask
  26.       // 并打印消息(但在实际应用中,这通常不是您想要的方式)
  27.       repeat
  28.         TaskMessage := TaskList.NextPendingTask;
  29.         if TaskMessage <> '' then
  30.           WriteLn('Executing Task: ', TaskMessage)
  31.         else
  32.           Break; // 没有更多待执行的任务,退出循环
  33.         // 在这里,我们实际上应该等待一段时间再检查下一个任务,
  34.         // 但为了简化示例,我们只是立即再次检查(这不是实际用法)
  35.       until False;
  36.     finally
  37.       // 销毁TPendingTaskList实例(在这个简单的示例中可能不是必需的,
  38.       // 但为了完整性而包含)
  39.       TaskList.Free;
  40.     end;
  41.   except
  42.     on E: Exception do
  43.       WriteLn('Error: ', E.Message);
  44.   end;
  45.   WriteLn('Program ended.');
  46. end.
复制代码
重要注意事项

  • 单线程执行:上面的示例是在单线程环境中运行的,因此它不会按预期等待任务实际执行。在实际应用中,您应该在一个单独的线程中或在事件循环中定期调用 NextPendingTask来检查并执行任务。
  • 模拟任务执行:为了简化示例,我们手动调用了 NextPendingTask并立即打印了消息。在实际应用中,您应该根据 NextPendingTask的返回值来决定是否执行任务,并且您可能需要等待一段时间再检查下一个任务。
  • 替换单元名称:请确保将 'YourSynapseUnit'替换为实际包含 TSynQueue和 TPendingTaskList类定义的单元名称。
  • 错误处理:示例中包含了基本的错误处理逻辑,但在实际应用中,您可能需要更详细的错误处理和日志记录。
  • 线程安全:尽管 TSynQueue和 TPendingTaskList是线程安全的,但在从多个线程访问它们时,您仍然需要确保正确地同步对它们的访问(尽管在这个简单的示例中我们没有这样做)。在实际应用中,您可能需要使用锁、信号量或其他同步机制来确保线程安全。然而,在这个特定的示例中,由于我们是在单线程环境中运行,因此不需要担心线程安全问题。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册