当我们将一个作业的执行委托给 Workers 这个包工头以后,那么这个作业实际上就处于三种状态之一:
- 排队中:作业在队列中等待被调度执行
- 运行中:作业正在执行
- 已完成:作业已经执行完,此时作业及相关连的数据都已自动被释放
由于已经完成的作业会被释放掉,所以,实际上,我们能得到的状态只有排队中和运行中两种,第三种由于作业已经不存在,我们就无法检测到其状态。
QWorker 提供了两个函数来跟踪作业的状态:PeekJobState 和 EnumJobStates,前者针对的是单个具体的作业,后者针对的是全部作业。在具体了解这两个函数之前,我们先看一下我们能得到的作业状态信息定义:
【Delphi】
/// <summary>作业状态</summary> TQJobState = record Handle: IntPtr; // 作业对象句柄 Proc: TQJobMethod; // 作业过程 Flags: Integer; // 标志位 IsRunning: Boolean; // 是否在运行中,如果为False,则作业处于队列中 Runs: Integer; // 已经运行的次数 EscapedTime: Int64; // 已经执行时间 PushTime: Int64; // 入队时间 PopTime: Int64; // 出队时间 AvgTime: Int64; // 平均时间 TotalTime: Int64; // 总执行时间 MaxTime: Int64; // 最大执行时间 MinTime: Int64; // 最小执行时间 NextTime: Int64; // 重复作业的下次执行时间 end;
【C++ Builder】
struct DECLSPEC_DRECORD TQJobState { public: NativeInt Handle; TQJobMethod Proc; int Flags; bool IsRunning; int Runs; __int64 EscapedTime; __int64 PushTime; __int64 PopTime; __int64 AvgTime; __int64 TotalTime; __int64 MaxTime; __int64 MinTime; __int64 NextTime; };
在调用 PeekJobState 时,如果找到作业,则 Handle 与传入的作业句柄一致,Proc 指向作业对应的过程句柄,Flags 是作业的标志位,保存的是 JOB_XXX 标志位,可以通过位与的方式来检查各个标志位是否设置,具体标志位定义参考下表:
JOB_RUN_ONCE : 作业只运行一次
JOB_IN_MAINTHREAD : 作业只能在主线程中运行
JOB_MAX_WORKERS : 尽可能多的开启可能的工作者线程来处理作业,暂不支持
JOB_LONGTIME : 作业需要很长的时间才能完成,以便调度程序减少它对其它作业的影响
JOB_SIGNAL_WAKEUP : 作业根据信号需要唤醒
JOB_TERMINATED : 作业不需要继续进行,可以结束了
JOB_GROUPED : 当前作业是作业组的一员
JOB_ANONPROC : 当前作业过程是匿名函数
JOB_FREE_OBJECT : Data关联的是Object,作业完成或清理时释放
JOB_FREE_RECORD : Data关联的是Record,作业完成或清理时释放
JOB_FREE_INTERFACE : Data关联的是Interface,作业完成时调用_Release
JOB_FREE_CUSTOM1 : Data关联的成员由用户指定的方式1释放
JOB_FREE_CUSTOM2 : Data关联的成员由用户指定的方式2释放
JOB_FREE_CUSTOM3 : Data关联的成员由用户指定的方式3释放
JOB_FREE_CUSTOM4 : Data关联的成员由用户指定的方式4释放
JOB_FREE_CUSTOM5 : Data关联的成员由用户指定的方式5释放
JOB_FREE_CUSTOM6 : Data关联的成员由用户指定的方式6释放
JOB_DATA_OWNER : 作业是Data成员的所有者
好了,现在我们来看 PeekJobState 的定义:
【Delphi】
/// <summary>获取指定作业的状态</summary> /// <param name="AHandle">作业对象句柄</param> /// <param name="AResult">作业对象状态</param> /// <returns>如果指定的作业存在,则返回True,否则,返回False</returns> /// <remarks> /// 1.对于只执行一次的作业,在执行完后不复存在,所以也会返回false /// 2.在FMX平台,如果使用了匿名函数作业过程,必需调用 ClearJobState 函数来执行清理过程,以避免内存泄露。 /// </remarks> function PeekJobState(AHandle: IntPtr; var AResult: TQJobState): Boolean;
【C++ Builder】
bool __fastcall PeekJobState(NativeInt AHandle, TQJobState &AResult);
请注意其中的注释中的 Remark 部分说明,PeekJobState 的返回值为安全起见,建议使用 ClearJobState 来释放返回的结果,以使匿名函数的引用计数在移动平台工作正常。
我们看下 ClearJobState 的实现:
procedure ClearJobState(var AState: TQJobState); begin if IsFMXApp then begin if (AState.Flags and JOB_ANONPROC) <> 0 then begin IUnknown(AState.Proc.ProcA)._Release; end; AState.Proc.Code := nil; AState.Proc.Data := nil; end; end;
可以看到,只是在 FMX 平台它起作业,VCL 中,由于 ProcA 定义为 TQJobProcA 所以会自动释放(FMX 平台 ProcA 的定义为 Pointer,定义为 TQJobProcA 无法编译)。
下面的代码是 PeekJobState 的一个例子:
var AState: TQJobState; S: String; ATime: Int64; ALoc: TQSymbolLocation; begin ATime := GetTimeStamp; if Workers.PeekJobState(FSignalWaitHandle, AState) then begin S := '作业 '; if (AState.Flags and JOB_ANONPROC) = 0 then begin if LocateSymbol(AState.Proc.Code, ALoc) then S := S + ' - ' + ALoc.FunctionName else S := S + TObject(AState.Proc.Data).MethodName(AState.Proc.Code); end else S := S + ' - 匿名函数'; if AState.IsRunning then S := S + ' 运行中:'#13#10 else S := S + ' 计划中:'#13#10; case AState.Handle and $03 of 0: begin S := S + ' 简单作业'#13#10; ShowMessage(S); Exit; end; 1: S := S + ' 重复作业(距下次执行时间: ' + FormatFloat('0.#', (AState.NextTime - ATime) / 10) + 'ms)'#13#10; 2: S := S + ' 信号作业'#13#10; end; S := S + ' 已运行:' + IntToStr(AState.Runs) + ' 次'#13#10 + ' 任务提交时间:' + RollupTime((ATime - AState.PushTime) div 10000) + ' 前'#13#10; if AState.PopTime <> 0 then S := S + ' 末次执行时间:' + RollupTime((ATime - AState.PopTime) div 10000) + ' 前' + #13#10; S := S + ' 平均每次用时:' + FormatFloat('0.#', AState.AvgTime / 10) + 'ms'#13#10 + ' 总计用时:' + FormatFloat('0.#', AState.TotalTime / 10) + 'ms'#13#10 + ' 最大用时:' + FormatFloat('0.#', AState.MaxTime / 10) + 'ms'#13#10 + ' 最小用时:' + FormatFloat('0.#', AState.MinTime / 10) + 'ms'#13#10; S := S + ' 标志位:'; if (AState.Flags and JOB_RUN_ONCE) <> 0 then S := S + '单次,'; if (AState.Flags and JOB_IN_MAINTHREAD) <> 0 then S := S + '主线程,'; if (AState.Flags and JOB_GROUPED) <> 0 then S := S + '已分组,'; if S[Length(S)] = ',' then SetLength(S, Length(S) - 1); ShowMessage(S); ClearJobState(AState); end else ShowMessage('未找到请求的句柄对应的作业,作业可能已经完成。'); end;
当然,上面的例子由于用到了 QMapSymbols 单元的函数,所以只能在 Delphi 的 Windows 下程序中运行。
与 PeekJobState 不同,EnumJobStates 是返回一个动态数组来包含所有的作业的状态,声明如下:
【Delphi】
/// <summary>枚举所有的作业状态</summary> /// <returns>返回作业状态列表</summary> /// <remarks>在FMX平台,如果使用了匿名函数作业过程,必需调用 ClearJobStates 函数来执行清理过程</remarks> function EnumJobStates: TQJobStateArray;
【C++ Builder】
TQJobStateArray __fastcall EnumJobStates(void);
这个和 PeekJobState 很类似,就不再赘述,直接上示例代码:
var AStates: TQJobStateArray; I: Integer; ATime: Int64; ALoc: TQSymbolLocation; ABuilder:TQStringCatHelperW; begin ATime := GetTimeStamp; AStates := Workers.EnumJobStates; ABuilder:=TQStringCatHelperW.Create; ABuilder.Cat('共发现 ').Cat(Length(AStates)).Cat(' 项作业'#13#10); for I := 0 to High(AStates) do begin if ABuilder.Position>1000 then begin ABuilder.Cat('...(后续省略)'); Break; end; ABuilder.Cat('作业 #').Cat(I + 1); if (AStates[I].Flags and JOB_ANONPROC) = 0 then begin if LocateSymbol(AStates[I].Proc.Code, ALoc) then ABuilder.Cat(' - ').cat(ALoc.FunctionName) else ABuilder.Cat(TObject(AStates[I].Proc.Data).MethodName(AStates[I].Proc.Code)); end else ABuilder.Cat(' - 匿名函数'); if AStates[I].IsRunning then ABuilder.Cat(' 运行中:'#13#10) else ABuilder.Cat(' 计划中:'#13#10); case AStates[I].Handle and $03 of 0: begin ABuilder.Cat(' 简单作业'#13#10); Continue; end; 1: ABuilder.Cat(' 重复作业(距下次执行时间: ' + FormatFloat('0.#', (AStates[I].NextTime - ATime) / 10) + 'ms)'#13#10); 2: ABuilder.Cat(' 信号作业'#13#10); end; ABuilder.Cat(' 已运行:').Cat(AStates[I].Runs).Cat(' 次'#13#10).Cat(' 任务提交时间: ').Cat( RollupTime((ATime - AStates[I].PushTime) div 10000)).Cat(' 前'#13#10); if AStates[I].PopTime <> 0 then ABuilder.Cat(' 末次执行时间: ').Cat(RollupTime((ATime - AStates[I].PopTime) div 10000)).Cat( ' 前'#13#10); ABuilder.Cat(' 平均每次用时:').Cat(FormatFloat('0.#', AStates[I].AvgTime / 10)).Cat('ms'#13#10) .Cat(' 总计用时:').Cat(FormatFloat('0.#', AStates[I].TotalTime / 10)).Cat('ms'#13#10).Cat( ' 最大用时:').Cat(FormatFloat('0.#', AStates[I].MaxTime / 10)).Cat('ms'#13#10).Cat( ' 最小用时:').Cat(FormatFloat('0.#', AStates[I].MinTime / 10)).Cat('ms'#13#10); ABuilder.Cat(' 标志位:'); if (AStates[I].Flags and JOB_RUN_ONCE) <> 0 then ABuilder.Cat('单次,'); if (AStates[I].Flags and JOB_IN_MAINTHREAD) <> 0 then ABuilder.Cat('主线程,'); if (AStates[I].Flags and JOB_GROUPED) <> 0 then ABuilder.Cat('已分组,'); ABuilder.Cat(SLineBreak); end; ClearJobStates(AStates); ShowMessage(ABuilder.Value); FreeObject(ABuilder); end;
在这个示例中,限制了下最多显示前1000个字节的内容,否则太长了,用 ShowMessage 显示会造成假死的现象。