如何在 2 个独立的 C# 程序之间进行事件驱动编程?

Posted

技术标签:

【中文标题】如何在 2 个独立的 C# 程序之间进行事件驱动编程?【英文标题】:How to do event driven programming between 2 separate C# programs? 【发布时间】:2010-11-23 06:42:27 【问题描述】:

如何从单独的 C# 程序中触发事件?

我正在处理的 C# 程序有两个独立的组件:任务创建器 (TC) 和任务处理器 (TP)。 TC 附加到一个不断向数据库表中插入新任务的 Web 服务。另一方面,TP 从同一个表中读取并处理每个任务,然后将状态更新回同一个表。它几乎就像一个队列,但使用数据库而不是例如完成。 MSMQ。每 5 秒 TP 唤醒并从表中读取以检查未处理的任务,但这会对性能产生一些影响。当没有新任务时,启动 select from where SQL 语句有点浪费。理想的方法是有某种通知方法,当插入新任务时,TP 会收到通知,然后从梦中醒来以检查新任务。

当前解决方案:

diagram of current solution http://yuml.me/3b49bcfd

理想的解决方案:

diagram of idealsolution http://yuml.me/5cf843a5

【问题讨论】:

【参考方案1】:

我会推荐 MSMQ。 :) 我不确定您为什么不使用它,但是您在问题中确实提到了它。这几乎正​​是 MSMQ 的设计目的……应用程序之间的持久事件。它主要支持发布/订阅消息模型...基于您的“理想解决方案”正是您所需要的:TC 是发布者,TP 是订阅者。 TC 注册任务,然后在其发布队列中删除一条消息。 TP 任务不需要启动并运行,TC 就可以成功将消息放入其队列中,但是当 TP 任务运行时,它将接收通知并按照到达的优先顺序处理队列中的消息。

如果 MSMQ 不是一个选项,您也可以使用 WCF。除了发布/订阅,您可以使用 WCF 选择 FAF(即发即弃)消息模型。 TP 会发布一个 TC 会消费的服务。 TC 只需要向 TP 的服务发送一条消息来通知 TP 新任务。该模型的缺点是 TC 依赖于 TP,这可能不太理想。 TP 还必须运行以使 TC 成功运行,因为它依赖于 TP 的服务。使用 MSMQ 方法,TP 和 TC 都不相互依赖,它们只依赖于 MSMQ(一种低耦合的方法)。

编辑:

如何使用 MSMQ 从 TC 触发事件并响应 TP 中的事件的示例。

// TC message queue manager, sends messages
public class TaskMessageQueueManager

  public void NotifySubscribersOfNewTasks()
  
    var queue = getQueue(".\private$\TaskNotifications");
    queue.Send("Tasks waiting.");
  

  private MessageQueue getQueue(string name)
  
    MessageQueue queue = null;
    try
    
      if (!MessageQueue.Exists(name))
      
        queue = MessageQueue.Create(name);
      
      else
      
        queue = new MessageQueue(name);
      
     
    catch (Exception ex)
    
      throw new InvalidOperationException("An error occurred while retrieving the message queue '" + name + "'.", ex);
    

    return queue;
  


// TP message queue handler, receives messages
public class TaskMessageQueueHandler

  private Thread m_thread;
  private ManualResetEvent m_signal;

  public void Start()
  
    m_signal = new ManualResetEvent(false);
    m_thread = new Thread(MSMQReceiveLoop);
    m_thread.Start();

  

  public void Stop()
  
    m_signal.Set();
  

  private void MSMQReceiveLoop()
  
    bool running = true;
    MessageQueue queue = getQueue(".\private$\TaskNotifications");

    while (running)
    
      try
      
        var message = queue.Receive(); // Blocks here until a message is received by MSMQ

        if (message.Body.ToString() == "Tasks waiting.")
        
          // TODO: Fire off process, perhaps another thread, to handle waiting tasks
        

        if (m_signal.WaitOne(10)) // Non-blocking check for exit signal
        
          running = false; // If Stop method has been called, the signal will be set and we can end loop
         
      
      catch
      
         // handle error
         running = false;
      
    
  

消息不必是简单的文本。您可以发送一个对象或对象图,默认情况下它会自动序列化并格式化为 XML。我相信你也可以以二进制格式序列化数据,如果你需要的话。无论哪种方式,您都会注意到任何地方都没有 Thread.Sleep 调用或轮询。循环基于 ManualResetEvent 退出,允许您干净地结束线程而无需硬中止。

【讨论】:

使用 MSMQ,队列读取器仍然需要定期休眠和唤醒。还是我错过了什么? 据我所知。使用 MSMQ 的 .NET 托管包装器,如果您在队列上调用 Recieve(),它会阻塞直到收到消息。只需设置一个处理循环,调用 Recieve(),当收到消息时,处理它。不应该比这更复杂。 正如你所说,我需要某种循环来调用 Receive() 这正是我想要摆脱的。 5秒睡眠/唤醒不是你说的循环吗? 不,我说的根本不是睡眠。 Recieve 调用 BLOCKS...这意味着您只需创建一个 while 循环,调用 myQueue.Recieve(),然后它就会等待。你不需要调用 sleep 来等待一段时间。阻塞调用会自动等待,监听事件。我会为你添加一个代码示例。【参考方案2】:

当没有新任务时,启动 select from where SQL 语句有点浪费。

在单个表上进行一次选择,每 5 秒使用简单的条件进行一次选择,并且没有返回任何行,通常几乎没有成本,不用担心。

【讨论】:

我只是简化案例,以便更容易提出问题。【参考方案3】:

查看SQL Server 2005 Query Notifications。我刚刚了解到它似乎是从 SQL Server 2008 中提取的,所以它可能不是最好的主意。

我认为 MSMQ 可能是一个更好的方法。结合nServiceBus,您可能会赢得胜利。

另一种方法可能是线程同步事件。

在 TP 中你可以有类似的东西:

EventWaitHandle taskEvent = new EventWaitHandle(true,
                EventResetMode.AutoReset,
                "newTask",
                out wasCreated);
new Thread(WaitForTask).Start();
...

public void WaitForTask()  while (true)  taskEvent.WaitOne(); ProcessTasks(); 

在 TC 中:

bool eventExist;
while (!eventExist)

    try
    
        taskEvent= EventWaitHandle.OpenExisting("newTask");
        eventExist = true;
    
    catch (WaitHandleCannotBeOpenedException)
    
        eventExist = false;
        Thread.Sleep(1000);
    


CreateNewTask();
taskEvent.Set();

我看不到每 5 秒一次的调用会如此消耗性能。

【讨论】:

我实际上并不支持 SQL Server 2005 Broker Service。我想我感兴趣的是实现某种不需要定期睡眠/唤醒的持续监控机制。携带“消息”的媒体可以是任何东西(MSMQ、数据库或其他)。我听说过 MassTransit / NServiceBus / Rhino.ServiceBus 但从未真正使用过它们。我可以通过使用这些库来消除睡眠/唤醒场景吗? NServiceBus 是一个围绕 MSMQ 的令人敬畏的 ESB。如果您需要 ESB 的所有好处,我强烈推荐它。如果您只需要基本事件,那么我会直接通过 .NET 托管 API 使用 MSMQ。 @Jeffrey,是的,您可以在消息到达关联队列时触发事件。我也同意@jrista 的观点,对于一个简单的场景,不需要服务总线,但既然你提到你确实简化了问题,我认为暗示它没有任何害处。 所以要自己回答这个问题,除非我使用 3rd 方将 2 个程序一起加入以交换方法参考,否则不可能触发事件。 "线程同步"我不确定,我会做更多的研究,谢谢指出。

以上是关于如何在 2 个独立的 C# 程序之间进行事件驱动编程?的主要内容,如果未能解决你的问题,请参考以下文章

将事件发送到独立应用程序

「事件驱动架构」事件溯源,CQRS,流处理和Kafka之间的多角关系

C# 命令绑定

在两个 C# 程序之间发送大量数据

如何使用 Linq 在 Mongo C# 2.2 驱动程序中查询嵌套列表?

如何在 C# 中使用事件驱动的 SAX(Simple API FOR XML) 解析器,还是使用 System.XML 命名空间更好?