Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Multi Process Architecture with C#

0.00/5 (No votes)
2 Jun 2009 1  
Separate functionality to run on child process

Introduction

This article is about running modules in independent child processes to isolate them from the main application. Server applications make the use of external modules in order to extend functionality. The classic implementation to this case is to define base interfaces and use factory method classes to load and use the modules from class DLLs. The problem starts when the number of modules increases. Since they run in the context of the server application, the application becomes unsafe regarding unexpected failure in any of the extensions. In other words, crash in one of the extension modules will kill the main application. Multi-Process Architecture means that each driver runs in its own child process. This approach separates the modules from the main server application and also each module from the other.

Background

There are few levels of module isolation.

The simplest way is using threading models. Each module runs in its own thread, while threading synchronization methods are used to access common resources. Threading makes the modules run concurrently but not so isolated.

.NET offers another way of isolation called AppDomain. Practically, it is different from threads in that it can be unloaded when misbehavior is detected. The drawback of AppDomain is that it stops the application when unhandled exception occurs. Using child process is the only way to protect the server application from unexpected behavior and failures in the modules.

The Multi-Process Architecture has been used lately by internet browsers that use tabs, like Internet Explorer 8 and Google Chrome. The display rendering functionality is isolated by using child process for each tab. It protects the browser application from misbehavior. Read more in Google Chrome Multi-process Architecture.

Disadvantage

The main problems of using multi-process architecture are:

  1. Each module consumes resources of process. It means more memory and maybe some other issues that should be considered.
  2. Duplex inter-processing communication should be added to the module interface.

The Server

On the server side, the code uses System.Diagnostics.Process and System.IO.Pipe.NamedPipeServerStream classes to manage the child process life time.

In the demo, thread is started to begin communicating with the child process.

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);
      }
    }

The Child Process

The code example demonstrates how to create a windowless child process and how to communicate with the parent using System.IO.Pipe.NamedPipeClientStream.

The first step is to create a Windows Form application and then use the Run method with the ApplicationContext class as shown below:

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]));
    }
  }

The AppContext implements ApplicationContext class that basically runs the message loop the same way as Windows.Form does. In this example, timer is started to send periodic 'Keep Alive' message to the parent application.

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;
      }
    }
  }

Inter-Process Communication (IPC)

The IPC method that I used is NamedPipe. In C#, we can use System.IO.Pipes from version .NET 3.5. In earlier frameworks, we must use wrappers for NamedPipe.

NamedPipe is a light weight approach for IPC. Using WCF, Remoting or simple networking is also acceptable.

Summary

The code example is just a simple demo that advices how to robust server and other applications that can't rely on third party implementations. The way Windows manages processes makes the modules run independently from the main application.

Moving to multi-process means doing some work, we must add IPC protocol instead of the existing simple interface. After I did this work in a large and busy server application, I must say that it was worth the effort, no more applications crash or hang.

History

  • 31st May, 2009: Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here