如何使多线函数成为原子

时间:2012-08-15 13:42:14

标签: delphi

我有一个必须完全运行的功能,不会中断,并返回一个结果。如果异步事件导致在它仍在执行时再次调用它,则必须以某种方式阻止该调用,直到第一个调用完成。

互斥?还有别的吗?


[更新] 该函数在我的主窗体类中,并从该类的两个方法调用:一个处理从serail端口读取的数据,另一个处理计时器到期。这两个似乎都在他们自己的线程中运行,因为一个函数的调用可以被另一个调用中断(对我来说,在应用程序级别,它们只是我在设计时放在主表单上的组件)。

也许TCriticalSection? (但谷歌搜索不清楚我是否需​​要使用acquire/release或'进入/离开'而且似乎因为代码是may mainform的一个简单函数,它将是可重入的)。

也许我要求的是一种使代码“不可重入”的方法,阻止直到第一个条目完成?无论答案是什么,我认为我需要一个代码示例,或者UTl到一个: - (

(请注意,this page提供了大量有用的信息(我不会全部了解,但对其他人来说看起来非常有用))


[更新] 这与GUI更新无关,只是每个发送一些TCOP数据的事件都应该接收响应。

虽然该应用程序有一个GUI - 它是基于表单的 - 只是在我想要显示一些调试信息的情况下,因为该应用程序将在没有监视器的PC上运行(是的,我知道它stil有一个GUI ,但那不是我的问题/要点)

4 个答案:

答案 0 :(得分:7)

将函数移动到没有异步事件的单独工作线程。

主线程中的异步事件应该将一些消息发布到某个队列中,因此当工作线程完成执行时,主线程将再次使用新参数启动它。


有更多信息的更新。 假设(猜测)这些函数通过RS232或者什么都没有接收数据,并且正在做类似GUI更新的事情,我将草拟以下方法。

  1. 使用表单的Windows消息队列进行警报。
  2. 使用一些互锁队列对象进行数据传递。 (是的,我知道我可以把指针放到Windows消息中,但我想要更多类型的安全性)
  3. 使用外部工作线程进行长时间处理。
  4. 更多详情:

    1. grep VCL源WM_USER并查看模式。您声明了3个消息ID常量:Work_Complete,Work_Request,Work_EmptyQueue。形式的通信消息方法。
    2. 我在OmniThreadLibrary中使用了准备好的线程安全队列。但是你可以继承System.Contnrs.TQueue或System.Generics.Collections.TQueue,将所有数据传递方法包装成关键部分。 总的来说,如果他们可以为您提供帮助,我建议您去OmniThreadLibrary管道上
    3. 这是硬件数据工作应用的标准方式。是它的MS-DOS设备驱动程序。或者一些嵌入式设备。您应该将快速和精益数据保存与长时间工作分开。
    4. 因此,RS232事件处理程序将从COM读取数据,将其放入TBytes,然后将该数据包添加到输入TQueue。 更复杂的方法是查看Queue是否已经包含来自COM的数据并使用旧的聚合新数据包而不是将新数据包作为单独的方式进行聚合。这需要更仔细的锁定,因此在这里聚合可能只是不值得蜡烛

      定时器甚至会使空数据包(零长度字节数组)并将其排队。 如果该计时器甚至还有一些数据需要通过 - 那么它应该是变量记录或单独的输入队列或其他什么。但没有信息似乎计时器只发送信息,但警报本身

      根据你的话,Timer和RS232事件都在它们各自的线程中工作。我对此表示怀疑,但我必须相信你。

      在将工作负载排入队列后(例如在输入队列的notify事件中),我会执行win32 PostMessage(MainForm.Handle,work_request)。毕竟我们想在一个地方集中线程控制。 要保持线程隔离消息应该发布,没有SendMessage没有TControl.Perform!

      在表单的work_request处理程序的主线程中,我会查看输入队列是否已经为空。如果没有,那么我会查看工作线程状态。如果它被暂停,我会恢复它。

      工作线程看起来是输入队列有什么东西,而它有,它会:

      1. 将队列中的数据包解压缩到本地var
      2. 做一些可能很长的处理
      3. 输出“工作结果”包。假设GUI更新可能只是键值对的集合,或者是一些数据字段的记录,其中数据字段已被填充,哪些数据字段被忽略。
      4. 将该数据包排入输出队列(队列然后将Work_Complete msg发布到主表单)
      5. 循环到1。
      6. 如果输入队列为空,则函数退出,线程执行PostMessage Work_EmptyQueue并暂停,直到稍后将被唤醒以执行更多工作或释放。

        当MainForm(再次在主线程中)收到Work_Complete时

        1. 将输出队列中的所有数据包提取到本地变量
        2. 合并他们。 (如果我有p1 =(label1 ='aaaa',label2 ='bbbb')和p2 =(label3 ='cccc',label1 ='dddd')则累计任务将被设置(label1 ='dddd',label2 ='bbbb',label3 ='cccc');
        3. 应用它们(实际更新GUI)
        4. 如果输出队列为空,则省略步骤2和3(如果在前一步骤中发生合并,则将省略)。但不是第四。 步骤1和2是分开的。由于队列操作是线程互锁的,因此步骤1的目标是尽可能快地提取数据。 Margine将在当地完成。

          当MainForm收到Work_EmptyQueue时,它会检查输入队列现在是否为空,并且可能会恢复工作线程。它可以选择对状态或其他任何内容进行GUI更新。

          这是一个粗略的草图。它可以更好地刻在你身上。

答案 1 :(得分:7)

假设您担心来自同一线程的重入,那么阻止重入调用将导致经典的死锁。如果这是您的方案,那么您需要执行以下操作之一:

  1. 确保无法进行重入调用,或
  2. 通过添加到队列中以便稍后处理来检测重新进入的呼叫并将其推迟,或者
  3. 检测可重入的电话,然后忽略它。
  4. 很可能这些选项都没有吸引你!

    如果您担心来自不同线程的同时呼叫,那么您可以使用某种锁。例如,在Windows上,您通常会使用关键部分。

答案 2 :(得分:5)

使用两种机制的组合:

  1. 使用TCriticalSection防止其他线程进入。显然,这只是必要的多线程应用程序。
  2. 使用再入门。这是一个简单的布尔值,可以防止从同一个线程重新进入。如果已输入您要保护的功能,则不要阻止,而是反弹。 “退回”意味着采取一些适当的措施,例如排队所需的活动以便以后执行。

答案 3 :(得分:1)

另一种方法是从其中一个事件源(例如,串行端口处理程序)进入关键功能时,在调用期间禁用其他事件源(到期计时器)。通话结束后重新启用它。您可以保证在通话期间无法触发其他事件源,因为计时器已被禁用。

对于在您要处理串行数据包时关闭到期计时器的情况,这很简单。如果我们假设到期定时器要跟踪在一段时间内没有从串行端口接收到任何内容,则禁用接收到的串行数据上的到期定时器是合适的。完成处理串行数据包后,恢复/重启到期时间。

相反的情况 - 在处理到期计时器时接收串行数据 - 可能有点棘手,因为您可能不希望通过禁用串行数据接收器来丢失数据。如果您的串行数据处理程序可以轻松暂停/恢复而不会丢失数据,那么您就完成了!否则,这里最好的选择可能是在对象实例中使用布尔标志来指示关键函数何时执行。

如果串行数据包进入并且该标志为真,则将串行数据包推入列表中以便稍后处理并立即返回。设置到期计时器处理程序,以便在从关键呼叫返回之前和之后立即检查该列表。如果列表非空,则处理列表中的所有项目。如果在您输入到期计时器处理程序时,列表中有数据包等待处理,您可能希望忽略到期计时器到期并继续进行操作。

在串行数据处理程序中,检查在调用关键函数之前和之后列表是否为非空。检查之前将有助于确保按接收顺序处理串行数据包。检查之后将确保在处理当前数据包期间到达的后续串行数据不会丢失。

如果有可能在不同的线程上调用串行数据事件处理程序,则应以线程安全的方式仔细处理对布尔标志和列表的更新。如果保证串行事件处理程序(和到期计时器处理程序)在UI线程上执行,那么您不必担心这一点。