none
Process 終了と 出力リダイレクトのタイミングが合いません RRS feed

  • 質問

  •  プログラム中で HTML ファイルを XML に成形するために、Process を使って 変換プログラム (HTML Tidy) を呼び出すコードを書いてみましたが、なぜか 入出力のリダイレクトが うまくいきません。

    (コードの一部)

    convProc.Start();
    convProc.BeginErrorReadLine();
    convProc.BeginOutputReadLine();
    convProc.StandardInput.Write(convSourceString);
    convProc.StandardInput.Close();

    if(!convProc.WaitForExit(30000))
      throw(new SystemException("プロセスがタイム アウトしました。"));

    print("----END----");

     

    問題は、WaitForExit は通過しているのに 非同期出力が終了せず、プロセスが閉じた後も DataReceivedEventHandler が呼び出されているようなんです。

    実際に Output/ErrorDataReceived イベントでテキスト出力すると、こういう感じです。

    Output.

    Output.

    Output.

    ...(中略)...

    Output.

    Output.

    Output.

    ----END----

    Error.

    Error.

    Output.

    Output.

    Error.

     

    …どうやら StandardOutput と StandardError の切り替え時に Exit してしまっているようなんですけれど…;

    Process.Close()、Refresh()、StreamReader.Close() などを入れて 色々試してみましたが、どうしても 非同期出力の終了とプロセスの終了を 合わせられません。

    何かやり方があるんでしょうか…?

    2010年3月20日 21:57

回答

  • StandardOutputとStandardErrorが読み終わるのをManualResetEventをつかって待機してみる

    using System;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                if(args.Length==0)
                {//引数が無い時は標準入力から読み込んで、送り返す
                    string read=System.Console.In.ReadToEnd();
                    System.Console.WriteLine(read);
                    System.Console.WriteLine(read);
                    System.Console.Write(read);
                }
                else
                {//引数があるときは引数なしで自分と同じ実行ファイルを実行する
                    try
                    {
                        System.Threading.ManualResetEvent[] waits = { errorClosed , outputClosed };
                        for (int loop = 0; loop < 100; loop++)
                        {
                            Console.WriteLine(string.Format("************** {0} **************" , loop));
    
                            errorClosed.Reset();
                            outputClosed.Reset();
    
                            System.Diagnostics.ProcessStartInfo info = new System.Diagnostics.ProcessStartInfo();
                            info.UseShellExecute = false;
                            info.RedirectStandardError = true;
                            info.RedirectStandardInput = true;
                            info.RedirectStandardOutput = true;
                            info.FileName = System.Reflection.Assembly.GetExecutingAssembly().Location;
    
                            using (System.Diagnostics.Process process = new System.Diagnostics.Process())
                            {
                                process.StartInfo = info;
                                process.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler(process_OutputDataReceived);
                                process.ErrorDataReceived += new System.Diagnostics.DataReceivedEventHandler(process_ErrorDataReceived);
    
                                process.Start();
                                process.BeginErrorReadLine();
                                process.BeginOutputReadLine();
    
                                process.StandardInput.Write(loop);
    
                                process.StandardInput.Close();
                                if (!process.WaitForExit(10000))
                                {
                                    throw new TimeoutException();
                                }
                                process.Close();
    
                                //OutputとErrorが閉じられるまで待機
                                if (!System.Threading.ManualResetEvent.WaitAll(waits , 10000))
                                {
                                    throw new TimeoutException();
                                }
    
                                Console.Out.WriteLine("END");
                            }
                        }
                    }
                    catch(TimeoutException)
                    {
                        Console.Error.Write("TimeOut");
                    }
                }
            }
    
            static System.Threading.ManualResetEvent errorClosed= new System.Threading.ManualResetEvent(false);
            static System.Threading.ManualResetEvent outputClosed = new System.Threading.ManualResetEvent(false);
    
            static void process_ErrorDataReceived(object sender , System.Diagnostics.DataReceivedEventArgs e)
            {
                if (String.IsNullOrEmpty(e.Data))
    
                {
                    Console.Out.WriteLine("CLOSE ErrorStream");
                    errorClosed.Set();
                }
                else
                                {
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.Error.Write(e.Data);
                    Console.ResetColor();
                }
            }
    
            static void process_OutputDataReceived(object sender , System.Diagnostics.DataReceivedEventArgs e)
            {
                if (string.IsNullOrEmpty(e.Data))
                {
                    Console.WriteLine("CLOSE OutputStream");
                    outputClosed.Set();
                }
                else
                {
                    Console.Out.WriteLine("OUT:" + e.Data);
                }
            }
        }
    }
    

    • 回答としてマーク にふてぃ 2010年3月26日 15:02
    2010年3月21日 10:49
  • timeoutなしのWaitForExitを使うと、同期するらしいですよ
    • 回答としてマーク にふてぃ 2010年3月26日 15:02
    2010年3月22日 3:45

すべての返信

  • StandardOutputとStandardErrorが読み終わるのをManualResetEventをつかって待機してみる

    using System;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                if(args.Length==0)
                {//引数が無い時は標準入力から読み込んで、送り返す
                    string read=System.Console.In.ReadToEnd();
                    System.Console.WriteLine(read);
                    System.Console.WriteLine(read);
                    System.Console.Write(read);
                }
                else
                {//引数があるときは引数なしで自分と同じ実行ファイルを実行する
                    try
                    {
                        System.Threading.ManualResetEvent[] waits = { errorClosed , outputClosed };
                        for (int loop = 0; loop < 100; loop++)
                        {
                            Console.WriteLine(string.Format("************** {0} **************" , loop));
    
                            errorClosed.Reset();
                            outputClosed.Reset();
    
                            System.Diagnostics.ProcessStartInfo info = new System.Diagnostics.ProcessStartInfo();
                            info.UseShellExecute = false;
                            info.RedirectStandardError = true;
                            info.RedirectStandardInput = true;
                            info.RedirectStandardOutput = true;
                            info.FileName = System.Reflection.Assembly.GetExecutingAssembly().Location;
    
                            using (System.Diagnostics.Process process = new System.Diagnostics.Process())
                            {
                                process.StartInfo = info;
                                process.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler(process_OutputDataReceived);
                                process.ErrorDataReceived += new System.Diagnostics.DataReceivedEventHandler(process_ErrorDataReceived);
    
                                process.Start();
                                process.BeginErrorReadLine();
                                process.BeginOutputReadLine();
    
                                process.StandardInput.Write(loop);
    
                                process.StandardInput.Close();
                                if (!process.WaitForExit(10000))
                                {
                                    throw new TimeoutException();
                                }
                                process.Close();
    
                                //OutputとErrorが閉じられるまで待機
                                if (!System.Threading.ManualResetEvent.WaitAll(waits , 10000))
                                {
                                    throw new TimeoutException();
                                }
    
                                Console.Out.WriteLine("END");
                            }
                        }
                    }
                    catch(TimeoutException)
                    {
                        Console.Error.Write("TimeOut");
                    }
                }
            }
    
            static System.Threading.ManualResetEvent errorClosed= new System.Threading.ManualResetEvent(false);
            static System.Threading.ManualResetEvent outputClosed = new System.Threading.ManualResetEvent(false);
    
            static void process_ErrorDataReceived(object sender , System.Diagnostics.DataReceivedEventArgs e)
            {
                if (String.IsNullOrEmpty(e.Data))
    
                {
                    Console.Out.WriteLine("CLOSE ErrorStream");
                    errorClosed.Set();
                }
                else
                                {
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.Error.Write(e.Data);
                    Console.ResetColor();
                }
            }
    
            static void process_OutputDataReceived(object sender , System.Diagnostics.DataReceivedEventArgs e)
            {
                if (string.IsNullOrEmpty(e.Data))
                {
                    Console.WriteLine("CLOSE OutputStream");
                    outputClosed.Set();
                }
                else
                {
                    Console.Out.WriteLine("OUT:" + e.Data);
                }
            }
        }
    }
    

    • 回答としてマーク にふてぃ 2010年3月26日 15:02
    2010年3月21日 10:49
  • timeoutなしのWaitForExitを使うと、同期するらしいですよ
    • 回答としてマーク にふてぃ 2010年3月26日 15:02
    2010年3月22日 3:45
  •  返信が遅れてすいません;

     

     教えて頂いた通り、無制限の WaitForExit なら 出力が終わってから抜けられました。

    ただ、Tidy が 強制出力モードでも たまにハングアップするようなので、qekka さんの回答のように 終了待ち処理を入れてみようと思います。

     

     ありがとうございました。

    2010年3月26日 15:02
  • timeoutが必要なら、timeoutありのWaitForExitで待って、timeoutしなかったらもう一度timeoutなしのWaitForExitすればよいかと
    2010年3月27日 8:56