Delphi OmniThreadLibrary + OPC 客户端

Posted

技术标签:

【中文标题】Delphi OmniThreadLibrary + OPC 客户端【英文标题】:Delphi OmniThreadLibrary + OPC Client 【发布时间】:2013-08-05 02:35:32 【问题描述】:

我正在使用单线程 OPC 客户端程序管理连接到同一 OPC 服务器的 3 个不同的西门子 PLC,同样由西门子提供。

单线程客户端如下所示:

loop
 begin
  processPLC1;
  processPLC2;
  processPLC3;
end;

每个processPLC程序都会调用底层的OPC库,例如:

 OPCutils.WriteOPCGroupItemValue(FGroupIf, FHandleItemOpc, Value);

好的,现在我想在不同的线程中调用每个 processPLC 并并行工作。

我做了一些研究并开始使用 OmniThreadLibrary 编写一些代码,但我不认为 OPC 代码是多线程安全的。是吗?

我应该使用 task.Invoke 还是类似的东西? ReadOPC 函数如何返回 PLC 标签的值? 这里的最佳实践是什么?

谢谢!!!

【问题讨论】:

【参考方案1】:

我通常认为这是通过两种方式完成的:

1) 应用程序具有由单个线程拥有的单个 OPC 客户端实例。所有由客户端应用程序自动化的并行进程然后在读取/写入 OPC 值时使用某种消息传递或与拥有 OPC 客户端的线程同步。

2) 每个线程都拥有自己的私有 OPC 客户端,每个客户端都与 OPC 服务器独立通信。

就我个人而言,我发现最常用的方案是前者;一个 OPC(或其他专有)客户端对象,带有对客户端进行同步调用的线程。确实,在几乎所有流程控制情况下,您都在使用多线程,目的是优雅地封装逻辑现实世界任务并将其与 UI 隔离,而不是为了任何类型的性能。这些线程可以承受等待数据的相对“较长”时间的阻塞——如果需要,PLC 会很高兴地按住堡垒一百毫秒。

您选择哪一种很大程度上取决于您的应用程序的规模和性质。对于花费很长时间等待外部实时事件的大量轻量级线程,在应用程序中保留单个 OPC 客户端实例并节省大量独立连接的开销是有意义的。对于少量繁重、快速移动、OPC 密集型线程,也许为每个线程提供自己的 OPC 客户端是有意义的。

还要记住您的 OPC 标签的刷新率 - 很多时候服务器仅以大约每 100 毫秒左右的顺序更新它们。即使是执行一次扫描,您的 PLC 也可能至少需要 10 毫秒。当数据永远不会那么快刷新时,让大量线程每秒独立轮询服务器一百次是没有意义的。

对于过程控制软件,您真正想要的是拥有大量空闲或低负载 CPU 时间 - 线程越轻越好。总体系统响应能力是关键,并且能够让您的软件处理高负载情况(在操作系统决定是时候索引 HDD 时,大量任务突然汇聚……可以说,净空保持齿轮润滑)。您的大多数线程可能大部分时间都在等待。事件和回调通常在这里最有意义。

此外,考虑 PLC 编程在这里也很重要。例如,在我的机器中,我有一些对时间非常关键的操作,同时,它们每次运行时都是唯一计时的——这些进程的持续时间大约为几分钟,计时到十分之一以下。一秒钟或更短的时间,每天重复数百到数千次,每次运行都具有关键但不同的持续时间。我已经看到这些处理方式有两种——一种在软件中,一种在 PLC 中。对于前一种情况,软件告诉 PLC 何时启动,然后一直运行,直到软件告诉它停止。这有明显的缺陷;在这种情况下,最好将时间间隔简单地发送到 PLC 并让它进行计时。突然间,所有时间/轮询压力都从软件中消失了,并且该过程可以优雅地处理自动化计算机崩溃等问题。如果您想对 OPC 服务器施加巨大压力以获取时间关键数据,则通常需要支付重新评估整个系统的设计——软件和PLC。

【讨论】:

感谢您的深入见解!不过,有一点不清楚:当我们谈论具有 100 毫秒周期时间的 OPC 服务器时,是否仅在使用 GroupAdvise 回调时才考虑该数字?如果我不等待标签更改回调,而是进行持续同步读取和写入,这些功能会受到服务器周期时间的影响吗? 100ms时间一般是标签数据在服务器上的默认刷新时间——它可以根据每个标签定制,并不是所有的实现都完全相同,但服务器一般不会在每次扫描的基础上反映 PLC 的内容。事实上,我见过比这更粗糙的系统(服务器PLC 和大型标签负载之间的 RS-232/485)。例如,您可以有五个客户端,每个客户端每 10 毫秒执行一次同步读取,服务器将为所有这些请求提供服务,但每个标签的值的变化速度不会超过其刷新率。【参考方案2】:

OPC 基于 COM 技术,因此适用相同的规则。如果要从不同的线程访问 OPC 服务器,每个线程必须自己调用 CoInitialize 和 CoUninitialize。

这很可能与从不同进程访问 OPC 服务器相同。 OPC 服务器本身是单线程还是多线程并不重要。

您正在使用的 OPC 客户端库的抽象可能会妨碍您。

【讨论】:

所以,基于 opcconnect.com/delphi.php 上的 Simple OPC Client,我的主线程调用HR := CoInitializeSecurity(...),然后ServerIf := CreateComObject(ProgIDToClassID(ServerProgID)) as IOPCServer; 连接到 OPC Server,然后创建组和每组的项目。现在我想并行三个不同的线程来处理不同的变量。在每个线程上,我应该在开始时调用CoInitialize,然后在每个线程内调用HR := ReadOPCGroupItemValue?我可以这样做还是应该关注关键部分等?谢谢! 不,您必须为每个线程获取一个单独的 IOPCServer 实例(CoInit...、CreateCom...)。不要在线程之间共享服务器接口!想象一下,您有多个客户端进程正在运行,而不是多个线程。你会怎么做?【参考方案3】:

恕我直言,您似乎对其进行了过度设计,最好使事情尽可能简单,但添加线程并不会使事情变得更简单。

如果您有多个 OPC 服务器,那么线程可能是一个更好的抽象,但现在您只需要一个连接到一个 OPC 服务器,那么拥有多个线程和多个连接似乎过于复杂而没有太多实际收益。

改为使用常规的 OPC 订阅机制进行顺序读取和写入,就像以前一样。

【讨论】:

我明白了!好吧,我的目标是,在将每台机器“优雅地封装”为不同的进程的同时,使其具有比大多数 OPC 客户端更面向对象的风格。另外,由于这个客户端是一个 GUI,我不希望有任何东西来阻止屏幕流和 DB 访问 好的,所以它是一个 gui 应用程序,那么是的,一个用于处理 opc 连接的线程最好不要影响 gui。我只是说添加线程很容易,以后解决同步问题很痛苦。 我还建议使用异步订阅来确保您的客户端应用程序不会花费时间等待 OPC 服务器响应。回调消息将通过 Windows 消息队列处理(如果您有一个单元线程应用程序),因此它们将与 UI 消息巧妙地交错。除非您对数据进行大量处理,否则您不需要任何额外的线程来显示数据。

以上是关于Delphi OmniThreadLibrary + OPC 客户端的主要内容,如果未能解决你的问题,请参考以下文章

如何知道 OmniThreadLibrary 中 Pipeline 阶段的状态?

如何确定主线程没有响应 Omni 线程库的原因?

delphi10和delphi2010区别

delphi编程

如何安装Delphi5

通过delphi执行DOS??