如何在 Delphi 中进行线程化的各种方式之间进行选择?

Posted

技术标签:

【中文标题】如何在 Delphi 中进行线程化的各种方式之间进行选择?【英文标题】:How Do I Choose Between the Various Ways to do Threading in Delphi? 【发布时间】:2011-03-26 02:22:07 【问题描述】:

看来我终于要在我的 Delphi 2009 程序中实现某种线程。如果只有一种方法可以做到这一点,我就会跑起来。但我看到了几种可能性。

谁能解释这些之间有什么区别以及为什么我会选择一个而不是另一个。

    Delphi 中的 TThread 类

    AsyncCallsAndreas Hausladen

    OmniThreadLibraryPrimoz Gabrijelcic (gabr)

    ...还有其他人吗?


编辑:

我刚刚阅读了 Gabr 在 2010 年 3 月(第 10 期)Blaise Pascal Magazine 上发表的一篇出色的文章,标题为“创建线程的四种方法”。您必须订阅才能获得该杂志的内容,因此受版权保护,我无法在此处复制任何关于它的实质性内容。

总的来说,Gabr 描述了使用 TThreads、直接 Windows API 调用、Andy 的 AsyncCalls 和他自己的 OmniThreadLibrary 之间的区别。他确实在最后得出结论:

“我并不是说除了经典的 Delphi 方式 (TThread) 之外,你必须选择其他任何东西,但知道你有哪些选择仍然是件好事”

Mghie 的回答非常彻底,并建议 OmniThreadLibrary 可能更可取。但是我仍然对每个人关于我(或任何人)应该如何为他们的应用程序选择线程方法的意见感兴趣。

您可以添加到列表中:

。 4. 直接调用 Windows API

。 5. Misha Charrett's CSI Distributed Application Framework 正如 LachlanG 在他的回答中所建议的那样。


结论:

我可能会选择 OmniThreadLibrary。我喜欢Gabr的作品。多年前我使用了他的分析器 GPProfile,而我目前正在使用他的 GPStringHash,它实际上是 OTL 的一部分。

一旦 Embarcadero 将该功能添加到 Delphi 中,我唯一关心的可能是升级它以使用 64 位或 Unix/Mac 处理。

【问题讨论】:

【参考方案1】:

如果您对多线程没有经验,您可能不应该从TThread 开始,因为它只是原生线程之上的一个薄层。我认为它的边缘也有点粗糙。自 Delphi 2 引入以来,它并没有太大的发展,主要是为了在 Kylix 时间框架内兼容 Linux,并纠正更明显的缺陷(比如修复损坏的 MREW 类,最后弃用 Suspend() 和 @987654323 @ 在最新的 Delphi 版本中)。

使用简单的线程包装类基本上也会导致开发人员专注于太低的级别。为了正确使用多个 CPU 内核,最好关注任务而不是线程,因为使用线程的工作分区不能很好地适应不断变化的需求和环境 - 取决于硬件和并行运行的其他软件的最佳数量线程可能会有很大差异,即使在同一系统上的不同时间也是如此。一个你只传递大量工作的库,并自动安排它们以充分利用可用资源,这在这方面有很大帮助。

AsyncCalls 是将线程引入应用程序的良好第一步。如果您的程序中有多个区域需要执行多个相互独立的耗时步骤,那么您可以通过将每个区域传递给 AsyncCalls 来简单地异步执行它们。即使您只有一个此类耗时的操作,您也可以异步执行它,并在 VCL 线程中简单地显示进度 UI,可选择允许取消操作。

AsyncCalls 对于在整个程序运行期间一直待在身边的后台工作人员而言,IMO 不太适用,并且当程序中的某些对象具有线程关联性(例如数据库连接或 OLE 对象可能具有要求所有调用都发生在同一个线程中)。

您还需要注意的是,这些异步操作属于“即发即弃”类型。每个重载的AsyncCall() 函数都会返回一个IAsyncCall 接口指针,如果您想避免阻塞,您可能需要保留对它的引用。如果不保留引用,则在引用计数为零的那一刻,接口将被释放,这将导致释放接口的线程等待异步调用完成。这是您在调试时可能会看到的情况,在退出创建 IAsyncCall 的方法时可能需要很长时间。

在我看来,OTL 是您的三个选项中最通用的一个,我会毫不犹豫地使用它。它可以做所有TThread 和 AsyncCalls 可以做的事情,还有更多。它有一个完善的设计,它足够高级,既让用户的生活变得轻松,又让一个 Unixy 系统的端口(同时保持大部分界面完好无损)看起来至少是可能的,如果不容易的话。在过去的几个月里,它还开始获得一些用于并行工作的高级构造,强​​烈推荐。

OTL 也有几十个样本,这对于入门很重要。 AsyncCalls 在 cmets 中只有几行代码,但由于其功能有限(它只做一件事,但做得很好),因此很容易理解。 TThread 只有一个样本,14 年没有真正改变,主要是如何不做事的例子。

无论您选择哪个选项,都没有库可以消除了解线程基础知识的需要。阅读有关这些方面的好书是任何成功编码的先决条件。例如,适当的锁定是所有这些的要求。

【讨论】:

反应很好。我刚刚开始探索向我的应用程序添加线程,该应用程序偶尔会与我的数据库进行冗长的对话,我真的希望它不要像失去线程的公共演讲者那样坐在那里。我只需要这种概览。【参考方案2】:

还有另一个鲜为人知的 Delphi 线程库,Misha Charrett 的 CSI Application Framework。

它基于消息传递而不是共享内存。相同的消息传递机制用于在同一进程或其他进程中运行的线程之间进行通信,因此它既是线程库,又是分布式进程间通信库。

开始有一点学习曲线,但一旦开始,您就不必担心所有传统线程问题,例如死锁和同步,框架会为您处理大部分问题。

Misha 多年来一直在开发此功能,并且一直在积极改进框架和文档。他总是对支持问题非常敏感。

【讨论】:

【参考方案3】:

TThread 是一个封装了 Windows 线程的简单类。您使用 Execute 方法创建一个后代类,其中包含该线程应执行的代码,创建线程并将其设置为运行并执行代码。

AsyncCalls 和 OmniThreadLibrary 都是在线程之上构建更高级别概念的库。它们是关于任务,您需要异步执行的离散工作。您启动库,它会设置一个任务池,一组特殊线程,其工作是等待直到您为它们工作,然后您将包含代码的函数指针(或方法指针或匿名方法)传递给库需要执行的,它在任务池线程之一中执行它并为您处理许多低级细节。

我没有太多地使用这两个库,所以我无法对两者进行比较。试一试,看看他们能做什么,哪一个让你感觉更好。

【讨论】:

【参考方案4】:

(对不起,我没有足够的积分来评论,所以我将其作为答案而不是对 OTL 的另一次投票)

我使用过 TThread、CSI 和 OmniThread (OTL)。这两个库都有不平凡的学习曲线,但比 TThread 更强大。我的结论是,如果你打算对线程做任何重要的事情,无论如何你最终都会编写一半的库功能,所以你最好从别人编写的工作、调试版本开始。 Misha 和 Gabr 都是比我们大多数人更好的程序员,所以他们的工作可能比我们做得更好。

我查看了 AsyncCalls,但它并没有达到我想要的效果。它确实有一个“同步”功能(OTL 中缺少),所以如果你依赖它,你可能会纯粹为此而使用 AynscCalls。 IMO 使用消息传递并不足以证明 Synchronize 的糟糕之处,因此请认真学习如何使用消息。

在这三个中,我更喜欢 OTL,主要是因为示例的集合,但也因为它更加独立。如果你已经在使用 JCL 或者你只在一个地方工作,这不是什么大问题,但是我会做一个混合工作,包括合同工作和销售客户安装 Misha 的系统比 OTL 更难,因为 OTL 大约有 20 个文件在一个目录中。这听起来很傻,但对很多人来说很重要。

使用 OTL,搜索示例和源代码的关键字以及在论坛中提问的组合对我来说很有效。我熟悉传统的“卸载 CPU 密集型任务”线程作业,但现在我正在处理一堆数据库工作,其中有更多的“线程阻塞等待 DB”和更少的“CPU 最大化”, OTL 在这方面工作得很好。主要区别在于我可以运行 30 多个线程而不会使 CPU 达到极限,但通常不可能停止一个。

【讨论】:

【参考方案5】:

我知道这不是最先进的方法 :-) 也许它也有局限性,但我只是尝试了 System.BeginThread 并发现它非常简单 - 可能是因为它的质量我指的是文档...http://www.delphibasics.co.uk/RTL.asp?Name=BeginThread(IMO Neil Moffatt 可以教 MSDN 一两件事)

这是我在尝试学习新事物时发现的最大因素,即文档的质量,而不是数量。只花了几个小时,然后我就回到了真正的工作上,而不是担心如何让线程完成它的业务。

编辑实际上 Rob Kennedy 在这里解释 BeginThread 做得很好BeginThread Structure - Delphi

编辑实际上就像 Rob Kennedy 在同一篇文章中解释 TThread 的方式一样,我想我明天会更改我的代码以使用 TThread。谁知道下周会是什么样子! (AsyncCalls 可能)

【讨论】:

我完全不同意。很难想象向初学者展示比在主线程之外调用 VCL GUI 函数更糟糕的事情。代码也没有设置IsMultiThread。如果在此之后对自己的代码进行建模,那么该阶段就会出现很多难以追踪的错误。 @mghie,哦,您指的是 Delphi Basics 页面上的示例。好吧,我只是将其视为如何启动单独线程的示例。我不认为它必然提倡从线程进行 GUI 操作。我很高兴我偶然发现了它。 TThread、AsyncCalls 或 OTL 将花费我几个小时(至少)来实现我想要的(这是 1:对多线程的简单介绍,以及 2:一段工作代码,以便我可以继续其余部分我的工作,而不是整天摆弄线程!)。现在我可以按照自己的节奏学习其他的了,不急:-) @mghie,顺便说一句,我以前读过 GUI 操作应该仅限于主线程,所以这确实是我所做的。我使用线程调用 Web 服务并将数据反馈给主线程,然后主线程更新 UI。现在效果很好。 @Sam:正如我在对这个问题的冗长回答中所写的那样 - 线程是如此困难,以至于 必须 投入时间来正确学习它。而且 IMO 最好将上述时间投入到像 OTL 这样的高级事物上,而不是投入到其他任何事情上。但是检查所有选项然后做出明智的决定也很有价值 - 如果有时间,那就是。在我的第一条评论中,我只想提一下,从长远来看,BeginThread() 可能比替代方案更困难,尤其是在您链接到的示例中使用它的方式。 @mghie,我不拥有自己的时间,我把它出租给那些可能不欣赏 OTL 与 BeginThread 更精细细节的人。我很想尝试 OTL(并学习消息传递),但我正在使用它不支持的 Delphi7。我要说的是,BeginThread 让我快速入门,而且因为它很快,我并没有在技术问题上浪费一整天,而是真正完成了工作。针对现实世界情况的真正解决方案。 :-)

以上是关于如何在 Delphi 中进行线程化的各种方式之间进行选择?的主要内容,如果未能解决你的问题,请参考以下文章

delphi中如何实现多线程对Canvas的同时访问

delphi多线程参数传递问题

Delphi - OTL - 线程池和工作线程之间的通信

关于在delphi中时间日期的转换问题,高手进

如何正确运用异步编程技术

如何正确运用异步编程技术