65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 中的多进程架构

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (24投票s)

2009年6月2日

CPOL

3分钟阅读

viewsIcon

152017

downloadIcon

6915

将功能分离到子进程中运行

引言

本文介绍在独立的子进程中运行模块,以将它们与主应用程序隔离。服务器应用程序使用外部模块来扩展功能。这种经典实现是定义基本接口并使用工厂方法类从类 DLL 加载和使用模块。当模块数量增加时,问题就开始了。由于它们在服务器应用程序的上下文中运行,应用程序在任何扩展中出现意外故障时变得不安全。换句话说,在任何扩展模块中崩溃都会导致主应用程序崩溃。多进程架构意味着每个驱动程序都在其自己的子进程中运行。这种方法将模块与主服务器应用程序以及每个模块与其他模块分开。

背景

模块隔离有几个级别。

最简单的方法是使用线程模型。每个模块在其自己的线程中运行,而线程同步方法用于访问公共资源。线程化使模块并发运行,但隔离程度不高。

.NET 提供了另一种隔离方法,称为 AppDomain。实际上,它与线程不同,因为它可以在检测到异常行为时卸载。AppDomain 的缺点是,当发生未处理的异常时,它会停止应用程序。使用子进程是保护服务器应用程序免受模块中意外行为和故障的唯一方法。

多进程架构最近被使用标签的互联网浏览器(如 Internet Explorer 8 和 Google Chrome)所采用。显示渲染功能通过为每个标签使用子进程来实现隔离。它保护浏览器应用程序免受异常行为的影响。在 Google Chrome 多进程架构 中阅读更多内容。

缺点

使用多进程架构的主要问题是

  1. 每个模块消耗进程资源。这意味着更多的内存,也可能需要考虑一些其他问题。
  2. 应将双工进程间通信添加到模块接口。

服务器

在服务器端,代码使用 System.Diagnostics.ProcessSystem.IO.Pipe.NamedPipeServerStream 类来管理子进程的生命周期。

在演示中,启动了一个线程以开始与子进程通信。

public bool Start(string paramUID)
    {
      m_PipeID = paramUID;

      m_PipeMessagingThread = new Thread(new ThreadStart(StartIPCServer));
      m_PipeMessagingThread.Name = this.GetType().Name + ".PipeMessagingThread";
      m_PipeMessagingThread.IsBackground = true;
      m_PipeMessagingThread.Start();

      ProcessStartInfo processInfo = new ProcessStartInfo
				("MultiProcessIPCClient", this.m_PipeID);
      m_ChildProcess = Process.Start(processInfo);

      return true;
    }

    /// Start the IPC server listener and wait for
    void StartIPCServer()
    {
      if (m_PipeServerStream == null)
      {
        m_PipeServerStream = new NamedPipeServerStream(m_PipeID,
                                                      PipeDirection.InOut,
                                                      1,
                                                      PipeTransmissionMode.Byte,
                                                      PipeOptions.Asynchronous,
                                                      BUFFER_SIZE,
                                                      BUFFER_SIZE);
      }

      try
      {
        //Wait for connection from the child process
        m_PipeServerStream.WaitForConnection();
        Console.WriteLine(string.Format("Child process {0} is connected.", m_PipeID));
      }
      catch (ObjectDisposedException exDisposed)
      {
        Console.WriteLine(string.Format("StartIPCServer for process {0} error: 
			{1}", this.m_PipeID, exDisposed.Message));
      }
      catch (IOException exIO)
      {
        Console.WriteLine(string.Format("StartIPCServer for process {0} error: 
			{1}", this.m_PipeID, exIO.Message));
      }

      //Start listening for incoming messages.
      bool retRead = true; ;
      while (retRead && !m_IsDisposing)
      {
        retRead = StartAsyncReceive();
        Thread.Sleep(30);
      }
    }

子进程

代码示例演示了如何创建无窗口子进程以及如何使用 System.IO.Pipe.NamedPipeClientStream 与父进程通信。

第一步是创建一个 Windows Form 应用程序,然后使用 Run 方法和 ApplicationContext 类,如下所示

static class Program
  {
    /// The main entry point for the application.
    [STAThread]
    static void Main(string[] args)
    {
      //Application.EnableVisualStyles();
      //Application.SetCompatibleTextRenderingDefault(false);
      //Application.Run(new Form1());
      Application.Run(new AppContext(args[0]));
    }
  }

AppContext 实现了 ApplicationContext 类,它基本上以与 Windows.Form 相同的方式运行消息循环。在此示例中,启动一个计时器以定期向父应用程序发送“保持活动”消息。

class AppContext : ApplicationContext
{
    private string m_ID;
    private NamedPipeClientStream m_PipeClientStream;
    System.Timers.Timer m_KeepAliveTimer = new System.Timers.Timer(2000);

    // Constructor
    public AppContext(string paramID)
    {
      // Handle the ApplicationExit event to know when the application is exiting.
      Application.ApplicationExit += new EventHandler(Application_ApplicationExit);
      m_ID = paramID;

      StartIPC();

      m_KeepAliveTimer.Elapsed += 
		new System.Timers.ElapsedEventHandler(m_KeepAliveTimer_Elapsed);
      m_KeepAliveTimer.Interval = 2000;
      m_KeepAliveTimer.Start();
    }

    // Sending message to the parent application
    void m_KeepAliveTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {
      ...
    }

    // Exiting the process
    void Application_ApplicationExit(object sender, EventArgs e)
    {
      System.Diagnostics.Trace.WriteLine("Application Exit " + m_ID);
    }

    // Connecting to the IPC server
    void StartIPC()
    {
      System.Diagnostics.Trace.WriteLine("Starting IPC client for " + m_ID);
      m_PipeClientStream = new NamedPipeClientStream(".",
                                                m_ID,
                                                PipeDirection.InOut,
                                                PipeOptions.Asynchronous);

      try
      {
        m_PipeClientStream.Connect(3000);
        System.Diagnostics.Trace.WriteLine("Connected to IPC server. " + m_ID);
      }
      catch(Exception ex)
      {
        System.Diagnostics.Trace.WriteLine("StartIPC Error for " + ex.ToString());
        return;
      }
    }
  }

进程间通信 (IPC)

我使用的 IPC 方法是 NamedPipe。在 C# 中,我们可以从 .NET 3.5 版本开始使用 System.IO.Pipes。在早期的框架中,我们必须使用 NamedPipe 的包装器。

NamedPipe 是一种轻量级的 IPC 方法。使用 WCF、Remoting 或简单的网络连接也是可以接受的。

摘要

代码示例只是一个简单的演示,它介绍了如何进行健壮的服务器和其他应用程序,这些应用程序不能依赖于第三方实现。Windows 管理进程的方式使得模块可以独立于主应用程序运行。

迁移到多进程意味着需要做一些工作,我们必须添加 IPC 协议而不是现有的简单接口。在大型且繁忙的服务器应用程序中完成这项工作后,我必须说,这是值得的,不再出现应用程序崩溃或挂起。

历史

  • 2009年5月31日:初始版本
© . All rights reserved.