Click here to Skip to main content
15,886,873 members
Articles / Programming Languages / C#
Article

The Process Async Reader Bug

Rate me:
Please Sign up or sign in to vote.
4.67/5 (2 votes)
15 Dec 2007CPOL2 min read 24.6K   16  
A workaround for a bug I discovered with the async reader in the Process class.

The Problem

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

C#
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:

C#
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:

C#
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:

C#
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:

C#
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:

C#
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)


Written By
Architect Interacx
United States United States
Blog: https://marcclifton.wordpress.com/
Home Page: http://www.marcclifton.com
Research: http://www.higherorderprogramming.com/
GitHub: https://github.com/cliftonm

All my life I have been passionate about architecture / software design, as this is the cornerstone to a maintainable and extensible application. As such, I have enjoyed exploring some crazy ideas and discovering that they are not so crazy after all. I also love writing about my ideas and seeing the community response. As a consultant, I've enjoyed working in a wide range of industries such as aerospace, boatyard management, remote sensing, emergency services / data management, and casino operations. I've done a variety of pro-bono work non-profit organizations related to nature conservancy, drug recovery and women's health.

Comments and Discussions

 
-- There are no messages in this forum --