210

次のコードがあります。

info = new System.Diagnostics.ProcessStartInfo("TheProgram.exe", String.Join(" ", args));
info.CreateNoWindow = true;
info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
info.RedirectStandardOutput = true;
info.UseShellExecute = false;
System.Diagnostics.Process p = System.Diagnostics.Process.Start(info);
p.WaitForExit();
Console.WriteLine(p.StandardOutput.ReadToEnd()); //need the StandardOutput contents

開始しているプロセスからの出力の長さは約 7MB です。Windows コンソールで実行すると問題なく動作します。残念ながら、プログラム的には、これは で無期限にハングしWaitForExitます。また、このコードは小さい出力 (3KB など) ではハングしないことに注意してください。

内部StandardOutputProcessStartInfo7MBをバッファリングできない可能性はありますか? もしそうなら、私は代わりに何をすべきですか?そうでない場合、私は何を間違っていますか?

4

21 に答える 21

442

問題は、リダイレクトした場合StandardOutputStandardError内部バッファがいっぱいになる可能性があることです。どのような順序で使用しても、問題が発生する可能性があります。

  • プロセスを読み取る前にプロセスが終了するのを待つと、プロセスへのStandardOutput書き込みがブロックされる可能性があるため、プロセスは決して終了しません。
  • StandardOutputReadToEnd を使用してから読み取る場合プロセスが閉じStandardOutputられない場合 (たとえば、プロセスが終了しない場合、または への書き込みがブロックされている場合StandardError) は、プロセスがブロックされる可能性があります。

解決策は、バッファがいっぱいにならないように非同期読み取りを使用することです。デッドロックを回避し、両方からのすべての出力を収集するにはStandardOutput、次のStandardErrorようにします。

編集:タイムアウトが発生した場合にObjectDisposedExceptionを回避する方法については、以下の回答を参照してください。

using (Process process = new Process())
{
    process.StartInfo.FileName = filename;
    process.StartInfo.Arguments = arguments;
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.RedirectStandardOutput = true;
    process.StartInfo.RedirectStandardError = true;

    StringBuilder output = new StringBuilder();
    StringBuilder error = new StringBuilder();

    using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false))
    using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false))
    {
        process.OutputDataReceived += (sender, e) => {
            if (e.Data == null)
            {
                outputWaitHandle.Set();
            }
            else
            {
                output.AppendLine(e.Data);
            }
        };
        process.ErrorDataReceived += (sender, e) =>
        {
            if (e.Data == null)
            {
                errorWaitHandle.Set();
            }
            else
            {
                error.AppendLine(e.Data);
            }
        };

        process.Start();

        process.BeginOutputReadLine();
        process.BeginErrorReadLine();

        if (process.WaitForExit(timeout) &&
            outputWaitHandle.WaitOne(timeout) &&
            errorWaitHandle.WaitOne(timeout))
        {
            // Process completed. Check process.ExitCode here.
        }
        else
        {
            // Timed out.
        }
    }
}
于 2011-09-30T10:05:02.207 に答える
108

ドキュメントには、Process.StandardOutput待つ前に読むように書かれています。

 // Start the child process.
 Process p = new Process();
 // Redirect the output stream of the child process.
 p.StartInfo.UseShellExecute = false;
 p.StartInfo.RedirectStandardOutput = true;
 p.StartInfo.FileName = "Write500Lines.exe";
 p.Start();
 // Do not wait for the child process to exit before
 // reading to the end of its redirected stream.
 // p.WaitForExit();
 // Read the output stream first and then wait.
 string output = p.StandardOutput.ReadToEnd();
 p.WaitForExit();
于 2008-09-26T13:49:43.590 に答える
23

Mark Byersの答えは素晴らしいですが、次のように追加します。

OutputDataReceivedおよびデリゲートは、およびをErrorDataReceived破棄する前に削除する必要があります。タイムアウトを超えた後もプロセスがデータを出力し続けて終了した場合、変数は破棄された後にアクセスされます。outputWaitHandleerrorWaitHandleoutputWaitHandleerrorWaitHandle

(参考までに、彼の投稿にコメントできなかったため、この警告を回答として追加する必要がありました。)

于 2012-04-10T10:23:24.353 に答える
19

未処理の ObjectDisposedException の問題は、プロセスがタイムアウトしたときに発生します。そのような場合、状態の他の部分:

if (process.WaitForExit(timeout) 
    && outputWaitHandle.WaitOne(timeout) 
    && errorWaitHandle.WaitOne(timeout))

実行されません。この問題を次の方法で解決しました。

using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false))
using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false))
{
    using (Process process = new Process())
    {
        // preparing ProcessStartInfo

        try
        {
            process.OutputDataReceived += (sender, e) =>
                {
                    if (e.Data == null)
                    {
                        outputWaitHandle.Set();
                    }
                    else
                    {
                        outputBuilder.AppendLine(e.Data);
                    }
                };
            process.ErrorDataReceived += (sender, e) =>
                {
                    if (e.Data == null)
                    {
                        errorWaitHandle.Set();
                    }
                    else
                    {
                        errorBuilder.AppendLine(e.Data);
                    }
                };

            process.Start();

            process.BeginOutputReadLine();
            process.BeginErrorReadLine();

            if (process.WaitForExit(timeout))
            {
                exitCode = process.ExitCode;
            }
            else
            {
                // timed out
            }

            output = outputBuilder.ToString();
        }
        finally
        {
            outputWaitHandle.WaitOne(timeout);
            errorWaitHandle.WaitOne(timeout);
        }
    }
}
于 2014-04-09T08:30:55.953 に答える
9

ロブはそれに答えて、さらに数時間の試行を節約しました。待機する前に出力/エラー バッファーを読み取ります。

// Read the output stream first and then wait.
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
于 2016-01-08T22:39:33.890 に答える
7

この問題 (または別の問題) もあります。

次のことを試してください。

1) p.WaitForExit(nnnn) にタイムアウトを追加します。ここで、nnnn はミリ秒単位です。

2) ReadToEnd 呼び出しを WaitForExit 呼び出しの前に置きます。これ、MS が推奨するものです。

于 2008-09-26T13:57:19.563 に答える
3

私はこのように解決しました:

            Process proc = new Process();
            proc.StartInfo.FileName = batchFile;
            proc.StartInfo.UseShellExecute = false;
            proc.StartInfo.CreateNoWindow = true;
            proc.StartInfo.RedirectStandardError = true;
            proc.StartInfo.RedirectStandardInput = true;
            proc.StartInfo.RedirectStandardOutput = true;
            proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;      
            proc.Start();
            StreamWriter streamWriter = proc.StandardInput;
            StreamReader outputReader = proc.StandardOutput;
            StreamReader errorReader = proc.StandardError;
            while (!outputReader.EndOfStream)
            {
                string text = outputReader.ReadLine();                    
                streamWriter.WriteLine(text);
            }

            while (!errorReader.EndOfStream)
            {                   
                string text = errorReader.ReadLine();
                streamWriter.WriteLine(text);
            }

            streamWriter.Close();
            proc.WaitForExit();

入力、出力、エラーの両方をリダイレクトし、出力ストリームとエラー ストリームからの読み取りを処理しました。このソリューションは、Windows 7 と Windows 8 の両方の SDK 7 ~ 8.1 で機能します。

于 2015-09-08T11:53:23.667 に答える
1

これはシンプルでより良いアプローチだと思います(必要ありませんAutoResetEvent):

public static string GGSCIShell(string Path, string Command)
{
    using (Process process = new Process())
    {
        process.StartInfo.WorkingDirectory = Path;
        process.StartInfo.FileName = Path + @"\ggsci.exe";
        process.StartInfo.CreateNoWindow = true;
        process.StartInfo.RedirectStandardOutput = true;
        process.StartInfo.RedirectStandardInput = true;
        process.StartInfo.UseShellExecute = false;

        StringBuilder output = new StringBuilder();
        process.OutputDataReceived += (sender, e) =>
        {
            if (e.Data != null)
            {
                output.AppendLine(e.Data);
            }
        };

        process.Start();
        process.StandardInput.WriteLine(Command);
        process.BeginOutputReadLine();


        int timeoutParts = 10;
        int timeoutPart = (int)TIMEOUT / timeoutParts;
        do
        {
            Thread.Sleep(500);//sometimes halv scond is enough to empty output buff (therefore "exit" will be accepted without "timeoutPart" waiting)
            process.StandardInput.WriteLine("exit");
            timeoutParts--;
        }
        while (!process.WaitForExit(timeoutPart) && timeoutParts > 0);

        if (timeoutParts <= 0)
        {
            output.AppendLine("------ GGSCIShell TIMEOUT: " + TIMEOUT + "ms ------");
        }

        string result = output.ToString();
        return result;
    }
}
于 2012-06-14T14:29:43.550 に答える
1

すべての複雑さを回避するために最終的に使用した回避策:

var outputFile = Path.GetTempFileName();
info = new System.Diagnostics.ProcessStartInfo("TheProgram.exe", String.Join(" ", args) + " > " + outputFile + " 2>&1");
info.CreateNoWindow = true;
info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
info.UseShellExecute = false;
System.Diagnostics.Process p = System.Diagnostics.Process.Start(info);
p.WaitForExit();
Console.WriteLine(File.ReadAllText(outputFile)); //need the StandardOutput contents

したがって、一時ファイルを作成し、出力とエラーの両方を使用してリダイレクトし > outputfile > 2>&1、プロセスが終了した後にファイルを読み取るだけです。

他のソリューションは、出力で他のことを行いたいシナリオでは問題ありませんが、単純なものの場合、これにより多くの複雑さが回避されます。

于 2019-06-07T08:32:21.893 に答える
1

ここのすべての投稿を読んだ後、Marko Avlijaš の統合ソリューションに落ち着きました。 ただし、すべての問題が解決したわけではありません。

私たちの環境では、何百もの異なる .bat .cmd .exe などのファイルを実行するようにスケジュールされた Windows サービスがあります。これらのファイルは長年にわたって蓄積され、さまざまな人々によってさまざまなスタイルで書かれています。プログラムとスクリプトの作成を制御することはできません。スケジューリング、実行、および成功/失敗の報告を担当するだけです。

そこで、さまざまなレベルの成功を収めて、ここにあるほとんどすべての提案を試しました。Marko の答えはほぼ完璧でしたが、サービスとして実行すると、常に stdout がキャプチャされるとは限りませんでした。私はなぜそうしないのかの底にたどり着きませんでした。

すべてのケースで機能することがわかった唯一の解決策は次のとおりです。http://csharptest.net/319/using-the-processrunner-class/index.html

于 2018-02-01T14:19:04.517 に答える
1

上記の答えはどれも仕事をしていません。

Robソリューションがハングし、「Mark Byers」ソリューションが破棄された例外を取得します(他の回答の「ソリューション」を試しました)。

そこで、別の解決策を提案することにしました。

public void GetProcessOutputWithTimeout(Process process, int timeoutSec, CancellationToken token, out string output, out int exitCode)
{
    string outputLocal = "";  int localExitCode = -1;
    var task = System.Threading.Tasks.Task.Factory.StartNew(() =>
    {
        outputLocal = process.StandardOutput.ReadToEnd();
        process.WaitForExit();
        localExitCode = process.ExitCode;
    }, token);

    if (task.Wait(timeoutSec, token))
    {
        output = outputLocal;
        exitCode = localExitCode;
    }
    else
    {
        exitCode = -1;
        output = "";
    }
}

using (var process = new Process())
{
    process.StartInfo = ...;
    process.Start();
    string outputUnicode; int exitCode;
    GetProcessOutputWithTimeout(process, PROCESS_TIMEOUT, out outputUnicode, out exitCode);
}

このコードはデバッグされ、完全に機能します。

于 2017-02-09T15:32:50.767 に答える
-1

この投稿は古くなっている可能性がありますが、通常ハングする主な原因は、redirectStandardoutput のスタック オーバーフローが原因であるか、redirectStandarderror がある場合であることがわかりました。

出力データまたはエラー データが大きいため、無期限に処理が継続されるため、ハング タイムが発生します。

この問題を解決するには:

p.StartInfo.RedirectStandardoutput = False
p.StartInfo.RedirectStandarderror = False
于 2011-03-26T08:28:29.087 に答える
-3

私は同じ問題を抱えていましたが、理由は異なっていました。ただし、Windows 8 では発生しますが、Windows 7 では発生しません。次の行が問題の原因のようです。

pProcess.StartInfo.UseShellExecute = False

解決策は、UseShellExecute を無効にしないことでした。シェル ポップアップ ウィンドウが表示されました。これは望ましくありませんが、特に何も起こらないのを待っているプログラムよりははるかに優れています。そのため、次の回避策を追加しました。

pProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden

今私を悩ませている唯一のことは、そもそもなぜこれが Windows 8 で起こっているのかということです。

于 2015-01-13T10:35:39.997 に答える