QWorker编程常见问题与解答

1、QWorker是做什么的?

请参考这篇文章:QWorker – Delphi编写的基于作业的跨平台多线程作业管理器,并阅读下 QWorker专题 中的内容,然后你就明白QWorker是做什么的了。

2、QWorker是线程安全的吗?

首先这个问题分两个方面:

一、QWorker本身是线程安全的,你可以放心的在多线程环境中触发信号或投寄作业。

二、作业本身如果牵涉到多线程,那么作业实现代码的部分的线程安全问题,由作业实现函数本身来负责。QWorker没有那种能力保证你实现代码的线程安全。

3、QWorker 独立使用都需要那些文件?

QWorker独立运行只需要以下文件:

QDAC.INC – 编译器条件编译项目兼容性定义

QWorker.pas – QWorker的主文件

QString.pas – 字符串及辅助处理函数公共单元

QRBTree.pas – 红黑树和哈希桶实现单元

QWinStackTracker.pas – Windows下用于实现基于DbgHelp的堆栈跟踪实现单元(仅 Windows 平台需要)

QMapSymbols.pas – 符号映射文件(.map)格式解析工具,用于提供函数地址到名称的解析(仅 Windows 平台需要)

如果您不需要跟踪堆栈和作业过程信息,可以取消QWorker的 USE_MAP_SYMBOLS 条件编译项目定义,这样 QMapSymbols 和 QWinStackTracker 两个单元也可以不要的。

4、QWorker工作者数量有什么限制?

QWorker默认的最大工作者数量为当前计算机的逻辑处理器数量*2+1,也就是说是你在 Windows 的任务管理器里看到的CPU数量*2+1就是了,比如一般的四核心4线程处理器,默认工作者数量是4*2+1=9个。当然这个数据偏向保守了一些,您可以修改 MaxWorkers 属性为一个合适的值,比如200。在满足业务需要的前提下,不推荐设置过多的工作者,但这取决于具体的实践环境。另外,Windows本身也对线程数量有一定的限制,内存和应用程序类型(32位或64位)也会实际上限制最大工作者数量。

QWorker最小的工作者数量被设定为2,无论您采用的是几核心的CPU。这个值可以通过属性 MinWorkers 来修改,但这个数量不允许小于1(你至少得留一个工人值班吧?)。

5、我投寄了一组主线程中的作业,它们为什么不是并行执行的?

这个,呃~~~~。首先你要知道什么是并行,就象你一个人,我给你一堆工作,您也是要一个个完成的。主线程在 QWorker 中实际就相当于一个工作者,您无论给它多少工作,它也只能干完一样再干另一样。但是,如果你不是用 TQJobGroup 来计划按顺序执行,那么,这些串行起来的作业串行的顺序并没有任何保证,也就是说后投寄的作业可能先执行完成。

6、我用 TQJobGroup 规划顺序执行作业,调用 TQJobGroup.Run(0) 时为什么只执行了一次?

呃~~~,你确信你看明白那个参数是干什么的了吗?那个参数是等待所有作业完成前的最大等待时间, 设置为0的话,会将作业投递之后立即尝试取消所有的作业。执行一次已经算您走运,实际上,极端情况下一次可能也不会执行就被取消了。

7、 TQJobGroup 的 Run 和 Wait / MsgWait 都有一个超时参数,它们有什么区别?

Run是启动作业执行后立即退出,而 Wait / MsgWait 则是等待所有组内作业完成。无论那个参数,如果到时作业仍未完成,那么将尝试取消所有剩余未执行的作业,并等待正在执行的作业完成后。

注意下 Run 和 Wait/MsgWait 的应用环境区别:

Run 设置超时是一般是用于异步等待作业完成。TQJobGroup 在所有组内作业完成,或者超时到达时,会触发 AfterDone 事件,用户可以在此处理异步作业执行结果。

Wait / MsgWait 本身是一种同步编程模型,它在执行一行代码之前,会等待所有组内作业完成或超时到达,然后返回并等待继续执行。当然它也会触发AfterDone事件,但很少有人会这么做。

至于 Wait 和 MsgWait 的区别在于前者如果运行在主线程中会造成主线程停止响应直到超时到或所有作业完成,而 MsgWait只会阻止MsgWait后续代码的执行,并不会阻塞主线程中其它代码的执行,也就是说,程序可以照常响应各种消息,更新UI等内容。如果在后台线程中执行,MsgWait 会实际调用 Wait 函数,两者没有任何区别。由于QWorker中主线程作业的执行是通过消息触发的,所以,如果你的作业组中包含了主线程作业,则你必需使用 MsgWait 代替 Wait 。

8、QWorker的定时执行与一般的定时器比有什么优势?

与 TTimer 这类一般的定时器相比,QWorker 的定时执行具有以下优势:

(1)、更灵活,支持定点执行(At),也可以实现定间隔执行(At / Post),还可以实现不考虑作业本身实际执行时间的延迟执行(Delay)。

(2)、精度更高,QWorker 的定时器最高可以支持 0.1ms 的高精度计时(具体精度取决于操作系统和计算机的处理能力及工作者空闲情况)。

(3)、没有数量限制。操作系统的定时器有数量限制,一个程序开过多的定时器会造成过多资源占用,而 QWorker 的定时执行资料消耗明显要小很多。

(4)、后台线程定时执行。QWorker 的定时执行的作业允许在主线程和后台线程中自由设置,方便您安排一些后台任务的计划执行。

9、我能自己创建 TQWorkers 实例不?

QWorkers 默认创建了一个全局的实例 Workers 。如果没有特殊需要,不推荐自己创建新的实例。当然这并不是限制,而只是不建议这么做,如果您确实需要,当然也可以这么做。只是一定要注意释放,避免不必要的内存泄露。

10、我能在DLL中使用 QWorker 吗?

当然可以,QWorker 的设计已经考虑到了让您在DLL中正常使用 QWorker 的情况,并在退出做了适当的处理。

11、QWorker 的 Post 和 Delay 都有时间间隔参数,有啥区别?

这个区别实际上是 Post 和 Delay 的区别,Post提交的作业如果有空闲的工作者,那么会立即被安排执行,而 Delay 首先要延迟指定的时间,然后才会安排执行。至于第一次执行后的执行,两者没有任何区别。

12、QWorker为什么默认创建一个全局的实例?

呃~~~,这是因为一个应用中正常只需要一个 TQWorkers 实例就够了。多余的实例除了增大开销后,绝大部分场景是没有任何意义的。与其现创建不如就全局创建一个,给大家省下代码更关注业务逻辑层的实现。

13、QWorker 可以清空所有队列吗?

理论上,不需要这么做。你可以调用Clear函数单独清除一个作业或者一类作业,为什么要全清呢?好吧,你非要全清,那么依然是调用 Clear 函数的不带任何参数的版本就好了。

14、QWorker 中要访问COM对象需要我自己每次调用 CoInitialize /CoInitializeEx 吗?

你只需在作业执行代码访问COM对象前调用 ComNeeded 函数即可。它可以保证当前工作者只调用一次 COM 初始化函数,而且在工作者完成时会自动调用 CoUninitialize。

15、QWorker 与 XE 7 自带的 System.Threading 并行开发库相比有什么优势?

呃~~~,本质上它们是基于不同的视角开发的。如果非要比较的话,QWorker 的功能更丰富,并且在并行编译方面支持从 2007 至最新 Delphi / C++ Builder 版本,而 System.Threading 明显你只能在 XE7 以后版本中使用。至于效率方面,QWorker 的 For 并行和 System.Threadings 的 For 并行之间几乎可以说是没有任何性能差距。

16、QWorker 的工作者为什么不能强行中止作业,而是要等待正在执行的作业自己退出?

这个问题首先要考虑几个问题:

(1)、根据 MSDN 中相关的文件说明,调用 TerminateThread 的话,线程本身的栈内存不会自己释放。而调用 ExitThread 按照 MSDN 的说法,也不是特别可靠的方案。

(2)、如果你在作业中分配了内存,而工作者强行中止的话,这部分内存就会泄露。

综上,通过强行结束工作者的方式硬性中止作业显然对于需要长期稳定运行的程序来说是不可以接受的。所以 QWorker 只接受协商式退出。在需要结束作业时,会设置作业的 IsTerminted 标志为True,作业需要自己检测这个标志位来决定是否安全的退出。

17、QWorker 在 Andriod 下可以用吗?

当然,QWorker 是跨平台的,不仅 Android,iOS、OSX 都可以用。

18、QWorker 在作业执行完成时,能不能触发什么事件?

这个,作者是感觉没啥必要。首先,作业函数是你自己实现的,你当然知道它啥时候完成,如果需要触发,你直接在其中触发,总比用工作者去转一下触发高效些。另外,如果确实需要 QWorker 帮你触发,你直接 Post 一个作业来通知不是更好吗?

19、QWorker 的作业中为什么我访问窗口中上的控件出现错误?

这个~~~~,我能告诉你不要试图在后台线程中访问窗体上的控件吗?几乎所有的开发语言界面控件都不是线程安全的。所以,在你作业中访问相关内容的部分,请调度一个主线程作业去完成吧。

20、QWorker 的 For 并行和 TQJobGroup 的并行我该如何取舍?

首先,要清楚For并行是有限制的。For的限制为:

(1)、它只会每个CPU使用一个工作者工作。

(2)、它必需等待所有的作业完成或调用BreakIt中止时,才会退出执行。

(3)、它的所有作业执行函数都是在后台线程中的执行的,不允许调度主线程作业。

(4)、同时只有一个作业函数在并发执行,不能同时运行不同的作业函数。

与 For 并行相比,TQJobGroup 的主要优势体现在:

(1)、更大的并发处理能力,实际的工作者数量仅受作业数量和 MaxWorkers 属性限制。

(2)、更灵活。

(2.1)、作业可以工作在主线程和后台线程中;

(2.2)、作业可以顺序执行,也可以并行执行;

(2.3)、可以同时运行不同的作业函数。

但是,For 与 TQJobGroup 相比,其调度开销更小,所以在能够使用 For 并行的场合,性能要比 TQJobGroup 更高。我们根据上面的比较可以基本确定一些原则:

(1)、For 不能用于需要在主线程中执行的作业;

(2)、For 适用于同时执行同一个函数的作业,同时运行不同的作业处理函数需要使用 TQJobGroup;

(3)、For 不能要求作业函数的执行顺序;

(4)、For 不应用于慢速 IO 牵涉到的计算;

在实际使用中,请根据上面的对比,自行选取。

21、QWorker 会支持 Free Pascal 吗?

这个暂时不会,因为 QWorker 用到了一些语法特性,FP并没有,所以暂时不能用。如果您有精力,可以尝试迁移下。

22、QWorker 的工作者怎么启动和停止?

QWorker 会自己调度工作者和作业,并管理各个工作者的生命周期,除非特殊情况,不需要手动停止工作者。如果你确实需要,在作业中,可以访问参数的 TQJob.Worker 属性来访问当前作业的调度线程。不过,作者并不推荐这么干。

23、QWorker 如何停止一个特定的作业?

QWorker 的作业在你投递时会返回一个句柄给你,如果你保存了这个句柄,就可以单独调用Workers.Clear来清理这个作业。如果作业正在执行,那么Clear将设置作业的 IsTerminated 标志位并等待作业自己主动退出。如果没有保存这个句柄,那么通过 Clear 时传递作业处理函数地址也可以清理相应的作业,但如果多个作业使用同一个清理函数,而你附加的数据没有区别或者没有附加额外的数据指针,则会全部清理掉。

 24、QWorker 如何实现执行时间不定,但执行完成需要间隔一段时间再次执行的定时作业?

这个很简单,只需要在作业函数完成时,调用 Workers.Delay 函数计划作业的下一次执行时间即可。 QWorker 本身是线程安全的,所以,你可以在作业中随时投递新的作业。

25、QWorker 如何实现工作流控制?

QWorker 提供 TQJobGroup 来分组和串行化作业,顺序执行的流程可以创建串行化的作业组控制,而并行化执行的当然就更不在话下了。在串行化作业控制下,你可以检查作业的控制条件是否满足,满足再执行下分组的下一个作业,如果不满足,调用分组的 Cancel 函数,取消后续步骤的执行就好了。简单的工作流控制引擎也就实现了。复杂的工作流控制完全可以通过在作业中触发新作业来控制。

26、QWorker 如何安装使用?

QWorker 不包括需要添加到组件面板上的可视功能,所以不需要安装,直接引用相关的文件即可。直接参考问题 3 将相应的文件拷贝到你的工程目录下,加入到工程中使用即可。Delphi 下使用 uses 直接加入 qworker 就可以,C++的话,加入工程文件直接编译会生成相应的 .hpp 文件,引用相关的 qworker.hpp 就可以使用了。

27、QWorker 中我怎么得到一个作业已经完成的通知?

QWorker 不认为自己实现一个通知比用你直接在作业处理代码结束时触发一个通知更高效简洁易用。但如果你非要用QWorker 来实现这一机制,那么,你可以用 TQJobGroup 封装一下作业,因为 TQJobGroup 在所有作业执行完成时,会触发 AfterDone 事件,从而得到一个通知。但就象前面所说的,这样一层封装作者认为是臃肿的,不必要的。

分享到:

4 条评论

沙发空缺中,还不快抢~