Click here to Skip to main content
Click here to Skip to main content

The Process Async Reader Bug

By , 15 Dec 2007
Rate this:
Please Sign up or sign in to vote.

The Problem

Let's say that you launch the same process twice from two different threads, like this:

using System;
using System.IO;
using System.Threading;

using Clifton.Timers;

namespace Launcher
{
  class Program
  {
    enum Process
    {
      Proc1, 
      Proc2,
    }

    static void Main(string[] args)
    {
      Thread t1 = new Thread(new ThreadStart(Launch1));
      Thread t2 = new Thread(new ThreadStart(Launch2));
      t1.Start();
      t2.Start();
      t1.Join();
      t2.Join();
    }

    static void Launch1()
    {
      DebugStopwatch.Start(Process.Proc1);
      LaunchAndWait(5000, "P1");
      DebugStopwatch.Stop(Process.Proc1);
      long t = 0;
      DebugStopwatch.ElapsedMilliseconds(Process.Proc1, ref t);
      Console.WriteLine("P1 took " + t + " ms");
    }

    static void Launch2()
    {
      DebugStopwatch.Start(Process.Proc2);
      LaunchAndWait(10000, "P2");
      DebugStopwatch.Stop(Process.Proc2);
      long t = 0;
      DebugStopwatch.ElapsedMilliseconds(Process.Proc2, ref t);
      Console.WriteLine("P2 took " + t + " ms");
    }

    static void LaunchAndWait(int ms, string procName)
    {
      Executable exec = new Executable(Path.GetFullPath(
         "..\\..\\..\\TestProcess\\bin\\debug\\TestProcess.exe"), ms.ToString());
      exec.Start();
      exec.WaitForExitInfinite();
      Console.WriteLine(procName + " done.");
    }
  }
}

The LaunchAndWait method calls WaitForExitInfinite, which is this:

public void WaitForExitInfinite()
{
  exe.WaitForExit();
}

where exe is an instance of the .NET Process class.

The process does nothing more than sleep for the amount of time specified in the command line arguments:

using System;
using System.Threading;

namespace TestProcess
{
  class Program
  {
    static void Main(string[] args)
    {
      Thread.Sleep(Convert.ToInt32(args[0]));
    }
  }
}

So, since Launch1 passes in 5000 and Launch2 passes in 10000, you would expect (correctly) that the result from the timers are:

P1 done.
P1 took 5091 ms
P2 done.
P2 took 10094 ms
Press any key to continue . . .

The class Executable, in this case, does nothing more than launch the process. Note the commented calls to begin the async output and error readers:

public void Start()
{
  exe = new Process();
  exe.StartInfo.FileName = filename;
  exe.StartInfo.UseShellExecute = false;
  exe.StartInfo.RedirectStandardInput = true;
  exe.StartInfo.RedirectStandardOutput = true;
  exe.StartInfo.RedirectStandardError = true;
  exe.StartInfo.CreateNoWindow = false;
  exe.StartInfo.Arguments = arguments;
  exe.ErrorDataReceived += new DataReceivedEventHandler(MonitorError);
  exe.OutputDataReceived += new DataReceivedEventHandler(MonitorOutput);
  exe.Start();
//exe.BeginOutputReadLine();
//exe.BeginErrorReadLine();
}

Now, watch--I'll uncomment the two lines above and rerun the test, the result of which is:

P1 done.
P2 done.
P1 took 10061 ms
P2 took 10061 ms
Press any key to continue . . .

So, simply by enabling async reading, the first process does not indicate that it is terminated until the second process terminates!

The Solution

The solution is to call Process.WaitForExit with a timeout, say 100ms. So, I change the LaunchAndWait to call WaitForExit instead of WaitForExitInfinite:

static void LaunchAndWait(int ms, string procName)
{
  Executable exec = new Executable(Path.GetFullPath(
       "..\\..\\..\\TestProcess\\bin\\debug\\TestProcess.exe"), ms.ToString());
  exec.Start();
  exec.WaitForExit();
  Console.WriteLine(procName + " done.");
}

Compare the WaitForExit call with the above WaitForExitInfinite call:

public void WaitForExit()
{
  bool running = true;

  while (running)
  {
    running = !exe.WaitForExit(100); 
  }
}

This call, while waiting forever as well, passes in a timeout value to Process.WaitForExit, and relies on the return code to test whether the process has terminated. With this call, the first process exits after 5 seconds and the second process after 10 seconds, as we would have expected.

Conclusion

I didn't discover this bug until I was testing a multithreaded movie re-encoder and the code wouldn't assign a new job to the first completed process until the second process had completed. Always suspecting my code first, I was dismayed to discover that this problem was with the async reader, which is vital in getting feedback from the re-encoder process. I had searched for other people that had encountered this problem but my search came up blank, so I ended up writing this article. If anyone has any further information about this problem, post a comment!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Marc Clifton

United States United States
Marc is the creator of two open source projets, MyXaml, a declarative (XML) instantiation engine and the Advanced Unit Testing framework, and Interacx, a commercial n-tier RAD application suite.  Visit his website, www.marcclifton.com, where you will find many of his articles and his blog.
 
Marc lives in Philmont, NY.

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web04 | 2.8.140421.2 | Last Updated 15 Dec 2007
Article Copyright 2007 by Marc Clifton
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid