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

How to override parent class event method with your own?

0.00/5 (No votes)
18 Oct 2003 1  
Use Process class to spawn many processes and keep an eye when they finish.

Background

While developing a database-dependent free web product, we had to write database-specific jobs in C#, which would otherwise be written in T-SQL, PL/SQL or DB2 SQL. Since our company did not want to invest in yet another big machine, we needed to run these jobs simultaneously from many different workstations. The database is hosted on a SMP machine and is capable of handling thousands of concurrent connections by using the load balancing failover feature of the Oracle 9i server. We wrote a racing multi-threaded C# batch program to maximize the CPU's ability to process the transactions. This program was run on several workstations which accessed a database server. The number of workstations was adjusted to keep the database server CPU utilization close to, but not greater than, 80%. We wrote an NT service to run these batch programs from different workstations. On a single workstation, several processes were started from the NT service which used different connection strings to the Oracle load balancing server. We had to explicitly specify the instance name in the processes as the Oracle load balancing server, since the server is good at balancing loads for on-line transactions, but not for batch jobs. In other words, instead of a big machine talking to another big machine, we were making several small machines to pound the big guy.

Introduction

The System.Diagnostic.Process class provides the mechanism to run a program in a separate process. We needed to spawn multiple processes of the same program or NT job script. We need to know when a job finishes and its performance, so we wrote a method to capture the standard output to a job specific file, which can be reviewed by operators. It was important to load balance the workstations through batch configuration files to optimize the time taken by each process and the number of transactions per process.

If we just want to execute a single process, this is a simple thing to do by attaching a hookup method to the Process.Exited event handler and capture the standard output for the process. The Process.Exited event handler raises the OnExit event when a process completes. The sample code here illustrates this:

Process p = new Process();
 
p.StartInfo.FileName = @"C:\WINDOWS\system32\ping.exe";
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.Arguments = @"www.google.com";
p.StartInfo.WorkingDirectory = @"C:\WINDOWS\system32";
p.EnableRaisingEvents = true;
p.StartInfo.UseShellExecute = false;
p.Exited += new EventHandler(captureStdout);
p.Start();
private void captureStdout(object sender, EventArgs e) 
{
    Process ps = (Process) sender;
    Console.WriteLine(ps.StandardOutput.ReadToEnd());
}

The object sender, when type cast to the process type, will tell you which process has exited. This is not even necessary if you run a single process, but for most cases, if you are handling multiple processes through an ArrayList in a loop, you would like to know the process ID or name that has completed its work.

What we really need is a little more than this, as we want to identify not only the exiting process, but also a few external parameters which are not related to the process attributes, for example, the connection string of the Oracle load balancing server and the batch name that we want to identify for each process that we spawn. When a process exits, we should be able to capture the time and other external attributes and log them to the database so that our Process Manager can synchronize the works in such a way that there is no conflict when the same service is run by different workstations.

The MSDN documentation suggested writing our own event handlers to pass data to events. We attempted to override the OnExited API method in our derived class from Process but we discovered that this API method is not marked virtual, abstract or override in its base class. I confirmed this by looking at the source code of this base class. I do not know the reason why there is a discrepancy in MSDN documentation, or am I missing something very trivial here? I am not an expert in C#, so if you know about it, please do let me know. We wrote a separate event handler and attached it to the Exited event handler so that our custom event handler comes into play when the parent process exits.

This custom event handler allows you to access the index of the process in the hookup method of the event handler. Without this, you will have to write a workaround in the code to cache the index of the process, and then flush it out when the process exits. We originally tried the second method, but this was cumbersome and ugly.

The steps that we followed are given below:

  1. Create a Custom EventArgs Class to pass event data.

    public class ProcessEventArgs : EventArgs
    {
      private readonly int index;
      public ProcessEventArgs (int p_index)
      {
        this.index = p_index; 
      }
      public int Index 
      {
        get {return index;}
      }
    }
  2. Create a Delegate.

    public delegate void 
      ProcessEventHandler(object sender, ProcessEventArgs e);
  3. Create a derived class to hijack when parent class raises OnExited event.

          public class ProcessManager : System.Diagnostics.Process
          {
                private int i;
                ProcessEventHandler onProcessExited;
                public ProcessManager()
                {}
     
                public ProcessManager(int i)
                {
                      this.i = i; 
                      this.Exited += new 
                         EventHandler(processManagerCapture);
                }
     
                public event ProcessEventHandler ProcessExited
                {
                      add 
                      {
                            onProcessExited += value;
                      }
                      remove 
                      {
                            onProcessExited -= value;
                      }
                }
     
                public void OnProcessExited()
                {
                      if (onProcessExited != null)
                            onProcessExited(this,new ProcessEventArgs(i));
                }
     
                private void processManagerCapture(object o, EventArgs e)
                {
                      OnProcessExited();
                }
     
          }

    In the above derived class, we need a mechanism to attach our event when the parent event OnExit is raised. When we instantiate the class, we subscribe to the base class Exited handler and attach it to the processManagerCapture method. This method raises our custom OnProcessExited event. Through the above implementation, you hide the functionality of the custom event handlers and let developers use the derived class.

  4. Implementation of the derived class.

    We use the ProcessExited event handler to pass the index of our ArrayList to the exit method. The implementation class shows this. We inherit the new class from ProcessManager, which has the implementation of our custom ProcessExited event handler. The BatchManager class runs the ping program with two different arguments in a separate process and captures the standard output along with the external attributes that we need.

    public class BatchManager : ProcessManager
    {
      ArrayList processArrayList;
      private string[] batchName = {"Batch 1","Batch 2"};
      private string[] pingSites = {"www.google.com","www.yahoo.com"};
     
      public BatchManager()
      {
      }
     
      public void Run()
      {
        try
        {
          RunPingProcess();
        }
        catch (Exception ex)
        {     
          Console.WriteLine(" Error while trying to " + 
            "Run ping process. Msg = " + ex.Message);
        }
      }
     
      private void RunPingProcess ()
      {
        processArrayList = new ArrayList();
        for (int i = 0; i < pingSites.Length; ++i)
        {
         processArrayList.Add(new ProcessManager(i));
         ((ProcessManager)processArrayList[i]).StartInfo.FileName = 
                     @"C:\WINDOWS\system32\ping.exe";
         ((ProcessManager)processArrayList[i]).StartInfo.RedirectStandardOutput
                                                                        = true;
         ((ProcessManager)processArrayList[i]).StartInfo.CreateNoWindow = true;
         ((ProcessManager)processArrayList[i]).StartInfo.Arguments = pingSites[i];
         ((ProcessManager)processArrayList[i]).StartInfo.WorkingDirectory = 
                         @"C:\WINDOWS\system32";
         ((ProcessManager)processArrayList[i]).EnableRaisingEvents = true;
         ((ProcessManager)processArrayList[i]).StartInfo.UseShellExecute = false;
         ((ProcessManager)processArrayList[i]).ProcessExited += 
         new ProcessEventHandler(NewCapture);
         ((ProcessManager)processArrayList[i]).Start();
        }
        for (int i = 0; i < pingSites.Length; ++i)
        {
          if (!((ProcessManager)processArrayList[i]).HasExited)
          {
            ((ProcessManager)processArrayList[i]).Refresh();
            ((ProcessManager)processArrayList[i]).WaitForExit();
          }
        }
      }
     
      private void NewCapture(object sender, ProcessEventArgs e)
      {
        ProcessManager pm = (ProcessManager) sender;
        Console.WriteLine("Batch Job " + batchName[e.Index] + ": Ping on " + 
        pingSites[e.Index] + " has completed.\nThe output of ping is:\n");
        Console.WriteLine(pm.StandardOutput.ReadToEnd());
        Console.WriteLine();
      }
    }

    Our sample program will work without a problem, since our standard output is not large. But if you have a batch process with a large standard output, the above hookup method will not work and will need to be modified. The reason for this is that, in the main loop we are waiting for the process to exit, but the hookup method reads all the standard output. Since the internal implementation is through a pipe, when the buffer gets filled, there will be a deadlock. The MSDN documentation suggests that you read the standard output before WaitForExit.

  5. Run as a Standalone Program.

    public class Test
    {
        public Test()
        {}
     
        static void Main()
        {
            BatchManager bm = new BatchManager();
            bm.Run();
        }
     
    }

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