ProcessStartInfo treo trên mạng WaitForExit '? Tại sao?


186

Tôi có đoạn mã sau:

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

Tôi biết rằng đầu ra từ quá trình tôi đang bắt đầu dài khoảng 7 MB. Chạy nó trong giao diện điều khiển Windows hoạt động tốt. Thật không may, lập trình này bị treo vô thời hạn tại WaitForExit. Cũng lưu ý rằng mã này KHÔNG treo cho các đầu ra nhỏ hơn (như 3KB).

Có thể là StandardOutput nội bộ trong ProcessStartInfo không thể đệm 7MB? Nếu vậy, tôi nên làm gì thay thế? Nếu không, tôi đang làm gì sai?


bất kỳ giải pháp cuối cùng với mã nguồn đầy đủ về nó?
Kiquenet

2
Tôi gặp vấn đề tương tự và đây là cách tôi có thể giải quyết vấn đề stackoverflow.com/questions/2285288/
dọa

6
Có, giải pháp cuối cùng: hoán đổi hai dòng cuối cùng. Đó là trong hướng dẫn .
Amit N Nikol

4
từ msdn: Ví dụ mã tránh tình trạng bế tắc bằng cách gọi p.St ChuẩnOutput.ReadToEnd trước p.WaitForExit. Một điều kiện bế tắc có thể xảy ra nếu tiến trình cha gọi p.WaitForExit trước p.St ChuẩnOutput.ReadToEnd và tiến trình con ghi đủ văn bản để điền vào luồng được chuyển hướng. Quá trình cha sẽ chờ vô thời hạn để tiến trình con thoát ra. Quá trình con sẽ chờ vô thời hạn để cha mẹ đọc từ luồng StandardOutput đầy đủ.
Carlos Liu

hơi khó chịu khi làm điều này đúng cách. Rất vui khi làm việc xung quanh nó với các chuyển hướng dòng lệnh đơn giản hơn> outputfile :)
eglasius

Câu trả lời:


392

Vấn đề là nếu bạn chuyển hướng StandardOutputvà / hoặc StandardErrorbộ đệm nội bộ có thể trở nên đầy đủ. Bất cứ thứ tự nào bạn sử dụng, có thể có một vấn đề:

  • Nếu bạn đợi quá trình thoát ra trước khi đọc StandardOutputquy trình có thể chặn việc cố gắng ghi vào nó, vì vậy quá trình không bao giờ kết thúc.
  • Nếu bạn đọc từ StandardOutputviệc sử dụng ReadToEnd thì quy trình của bạn có thể chặn nếu quy trình không bao giờ đóng StandardOutput(ví dụ nếu nó không bao giờ chấm dứt hoặc nếu nó bị chặn ghi vào StandardError).

Giải pháp là sử dụng các lần đọc không đồng bộ để đảm bảo rằng bộ đệm không đầy. Để tránh mọi bế tắc và thu thập tất cả đầu ra từ cả hai StandardOutputStandardErrorbạn có thể làm điều này:

EDIT: Xem câu trả lời bên dưới để biết cách tránh ObjectDisposedException nếu thời gian chờ xảy ra.

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.
        }
    }
}

11
Không có ý tưởng chuyển hướng đầu ra đã gây ra vấn đề nhưng chắc chắn nó đã được. Đã dành 4 giờ đập đầu tôi vào việc này và sửa nó trong 5 phút sau khi đọc bài viết của bạn. Công việc tốt đẹp!
Ben Gripka

1
@AlexPeck Vấn đề là chạy ứng dụng này. Hans Passant đã xác định vấn đề ở đây: stackoverflow.com/a/16218470/279516
Bob Horn

5
Mỗi khi dấu nhắc lệnh đóng lại, điều này sẽ xuất hiện: Một ngoại lệ chưa được xử lý của loại "System.ObjectDisposed" xảy ra trong mscorlib.dll Thông tin bổ sung:
Xử

3
Chúng tôi đã có một vấn đề tương tự như được mô tả bởi @ user1663380 ở trên. Bạn có nghĩ rằng nó có thể là usingbáo cáo cho xử lý sự kiện cần phải được trên các usingtuyên bố cho quá trình tự?
Dan Forbes

2
Tôi không nghĩ rằng các tay cầm chờ là cần thiết. Theo msd, chỉ cần kết thúc với phiên bản không thời gian chờ của WaitForExit: Khi đầu ra tiêu chuẩn đã được chuyển hướng đến các trình xử lý sự kiện không đồng bộ, có thể việc xử lý đầu ra sẽ không hoàn thành khi phương thức này trở lại. Để đảm bảo rằng việc xử lý sự kiện không đồng bộ đã được hoàn thành, hãy gọi quá tải WaitForExit () không có tham số nào sau khi nhận được sự thật từ tình trạng quá tải này.
Patrick

98

Các tài liệu cho Process.StandardOutputbiết để đọc trước khi bạn chờ đợi nếu không bạn có thể bế tắc, đoạn mã sao chép dưới đây:

 // 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();

14
Tôi không chắc chắn 100% nếu đây chỉ là kết quả của môi trường của tôi, nhưng tôi đã tìm thấy nếu bạn đã thiết lập RedirectStandardOutput = true;và không sử dụng, p.StandardOutput.ReadToEnd();bạn sẽ gặp bế tắc / treo cổ.
Chris S

3
Thật. Tôi đã ở trong tình trạng tương tự. Tôi đã chuyển hướng StandardError mà không có lý do gì khi chuyển đổi với ffmpeg trong một quy trình, nó đã ghi đủ trong luồng StandardError để tạo bế tắc.
Léon Pelletier

Điều này vẫn treo cho tôi ngay cả với chuyển hướng và đọc đầu ra tiêu chuẩn.
dùng3791372

@ user3791372 Tôi đoán điều này chỉ có thể áp dụng nếu bộ đệm phía sau StandardOutput không được điền đầy đủ. Ở đây MSDN không làm công lý của nó. Một bài viết tuyệt vời mà tôi muốn giới thiệu cho bạn đọc là tại: dzone.com/articles/async-io-and-threadpool
Cary

19

Câu trả lời của Mark Byers là tuyệt vời, nhưng tôi chỉ cần thêm vào như sau:

Các OutputDataReceivedErrorDataReceivedđại biểu cần phải được gỡ bỏ trước outputWaitHandleerrorWaitHandleđược xử lý. Nếu quá trình tiếp tục xuất dữ liệu sau khi hết thời gian chờ và sau đó chấm dứt, các biến outputWaitHandleerrorWaitHandlebiến sẽ được truy cập sau khi được xử lý.

(FYI tôi đã phải thêm lời cảnh báo này như một câu trả lời vì tôi không thể bình luận về bài đăng của anh ấy.)


2
Có lẽ sẽ tốt hơn nếu gọi CancOutputRead ?
Mark Byers

Thêm mã chỉnh sửa của Mark vào câu trả lời này sẽ khá tuyệt vời! Tôi đang có cùng một vấn đề chính xác vào phút này.
ianbailey

8
@ianbailey Cách dễ nhất để giải quyết vấn đề này là đặt cách sử dụng (Quá trình p ...) bên trong bằng cách sử dụng (AutoResetEvent errorWaitHandle ...)
Didier A.

17

Sự cố với ObjectDisposedException chưa được xử lý xảy ra khi quá trình hết thời gian. Trong trường hợp như vậy, các phần khác của điều kiện:

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

không được thực thi. Tôi đã giải quyết vấn đề này theo cách sau:

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);
        }
    }
}

1
vì mục đích hoàn chỉnh, điều này còn thiếu khi thiết lập các chuyển hướng thành đúng
knocte

và tôi đã loại bỏ hết thời gian chờ vì quá trình có thể yêu cầu đầu vào của người dùng (ví dụ: nhập nội dung nào đó) vì vậy tôi không muốn yêu cầu người dùng phải nhanh
knocte

Tại sao bạn đã thay đổi outputerrorthành outputBuilder? Ai đó có thể xin vui lòng cung cấp câu trả lời đầy đủ mà làm việc?
Marko Avlijaš

System.ObjectDisposedException: Xử lý an toàn cũng đã bị đóng trên phiên bản này đối với tôi
Matt

17

Đây là một giải pháp dựa trên Thư viện song song (TPL) hiện đại hơn đang chờ đợi cho .NET 4.5 trở lên.

Ví dụ sử dụng

try
{
    var exitCode = await StartProcess(
        "dotnet", 
        "--version", 
        @"C:\",
        10000, 
        Console.Out, 
        Console.Out);
    Console.WriteLine($"Process Exited with Exit Code {exitCode}!");
}
catch (TaskCanceledException)
{
    Console.WriteLine("Process Timed Out!");
}

Thực hiện

public static async Task<int> StartProcess(
    string filename,
    string arguments,
    string workingDirectory= null,
    int? timeout = null,
    TextWriter outputTextWriter = null,
    TextWriter errorTextWriter = null)
{
    using (var process = new Process()
    {
        StartInfo = new ProcessStartInfo()
        {
            CreateNoWindow = true,
            Arguments = arguments,
            FileName = filename,
            RedirectStandardOutput = outputTextWriter != null,
            RedirectStandardError = errorTextWriter != null,
            UseShellExecute = false,
            WorkingDirectory = workingDirectory
        }
    })
    {
        var cancellationTokenSource = timeout.HasValue ?
            new CancellationTokenSource(timeout.Value) :
            new CancellationTokenSource();

        process.Start();

        var tasks = new List<Task>(3) { process.WaitForExitAsync(cancellationTokenSource.Token) };
        if (outputTextWriter != null)
        {
            tasks.Add(ReadAsync(
                x =>
                {
                    process.OutputDataReceived += x;
                    process.BeginOutputReadLine();
                },
                x => process.OutputDataReceived -= x,
                outputTextWriter,
                cancellationTokenSource.Token));
        }

        if (errorTextWriter != null)
        {
            tasks.Add(ReadAsync(
                x =>
                {
                    process.ErrorDataReceived += x;
                    process.BeginErrorReadLine();
                },
                x => process.ErrorDataReceived -= x,
                errorTextWriter,
                cancellationTokenSource.Token));
        }

        await Task.WhenAll(tasks);
        return process.ExitCode;
    }
}

/// <summary>
/// Waits asynchronously for the process to exit.
/// </summary>
/// <param name="process">The process to wait for cancellation.</param>
/// <param name="cancellationToken">A cancellation token. If invoked, the task will return
/// immediately as cancelled.</param>
/// <returns>A Task representing waiting for the process to end.</returns>
public static Task WaitForExitAsync(
    this Process process,
    CancellationToken cancellationToken = default(CancellationToken))
{
    process.EnableRaisingEvents = true;

    var taskCompletionSource = new TaskCompletionSource<object>();

    EventHandler handler = null;
    handler = (sender, args) =>
    {
        process.Exited -= handler;
        taskCompletionSource.TrySetResult(null);
    };
    process.Exited += handler;

    if (cancellationToken != default(CancellationToken))
    {
        cancellationToken.Register(
            () =>
            {
                process.Exited -= handler;
                taskCompletionSource.TrySetCanceled();
            });
    }

    return taskCompletionSource.Task;
}

/// <summary>
/// Reads the data from the specified data recieved event and writes it to the
/// <paramref name="textWriter"/>.
/// </summary>
/// <param name="addHandler">Adds the event handler.</param>
/// <param name="removeHandler">Removes the event handler.</param>
/// <param name="textWriter">The text writer.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public static Task ReadAsync(
    this Action<DataReceivedEventHandler> addHandler,
    Action<DataReceivedEventHandler> removeHandler,
    TextWriter textWriter,
    CancellationToken cancellationToken = default(CancellationToken))
{
    var taskCompletionSource = new TaskCompletionSource<object>();

    DataReceivedEventHandler handler = null;
    handler = new DataReceivedEventHandler(
        (sender, e) =>
        {
            if (e.Data == null)
            {
                removeHandler(handler);
                taskCompletionSource.TrySetResult(null);
            }
            else
            {
                textWriter.WriteLine(e.Data);
            }
        });

    addHandler(handler);

    if (cancellationToken != default(CancellationToken))
    {
        cancellationToken.Register(
            () =>
            {
                removeHandler(handler);
                taskCompletionSource.TrySetCanceled();
            });
    }

    return taskCompletionSource.Task;
}

2
câu trả lời hay nhất và đầy đủ nhất cho đến nay
TermoTux

1
Đối với một số reaon, đây là giải pháp duy nhất hiệu quả với tôi, ứng dụng đã ngừng treo.
Jack

1
Có vẻ như, bạn không xử lý điều kiện, nơi quá trình kết thúc sau khi nó bắt đầu, nhưng trước khi sự kiện Exited được đính kèm. Đề nghị của tôi - bắt đầu quá trình sau khi đăng ký.
Stas Boyarincev

@StasBoyarincev Cảm ơn, đã cập nhật. Tôi đã quên cập nhật câu trả lời StackOverflow với thay đổi này.
Muhammad Rehan Saeed

1
@MuhammadRehanSaeed Một điều nữa - dường như không được phép gọi process.BeginOutputReadLine () hoặc process.BeginErrorReadLine () trước process.Start. Trong trường hợp này, tôi gặp lỗi: StandardOut chưa được chuyển hướng hoặc quá trình chưa bắt đầu.
Stas Boyarincev

8

Rob đã trả lời và tiết kiệm cho tôi thêm vài giờ thử nghiệm. Đọc bộ đệm đầu ra / lỗi trước khi chờ:

// Read the output stream first and then wait.
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();

1
Nhưng nếu nhiều dữ liệu đến sau khi bạn đã gọi WaitForExit()thì sao?
knocte

@knocte dựa trên các thử nghiệm của tôi ReadToEndhoặc các phương thức tương tự (như StandardOutput.BaseStream.CopyTo) sẽ trở lại sau khi đọc TẤT CẢ dữ liệu. không có gì sẽ đến sau nó
S.Serpooshan

bạn đang nói rằng ReadToEnd () cũng đang chờ lối ra?
knocte

2
@knocte Bạn đang cố gắng hiểu ý nghĩa của API được tạo bởi microsoft?
aaaaaa

Vấn đề của trang MSDN tương ứng là, nó đã không giải thích rằng bộ đệm phía sau StandardOutput có thể bị đầy và trong tình huống đó, đứa trẻ phải dừng viết và đợi cho đến khi bộ đệm bị cạn kiệt (cha mẹ đọc dữ liệu trong bộ đệm) . ReadToEnd () chỉ có thể đồng bộ đọc-ly cho đến khi bộ đệm được đóng hoặc bộ đệm đầy, hoặc con thoát ra với bộ đệm không đầy. Đó là sự hiểu biết của tôi.
Cary

7

Chúng tôi có vấn đề này là tốt (hoặc một biến thể).

Hãy thử như sau:

1) Thêm thời gian chờ vào p.WaitForExit (nnnn); trong đó nnnn tính bằng mili giây.

2) Đặt cuộc gọi ReadToEnd trước cuộc gọi WaitForExit. Đây những gì chúng tôi đã thấy MS đề nghị.


5

Tín dụng cho EM0 cho https://stackoverflow.com/a/17600012/4151626

Các giải pháp khác (bao gồm cả EM0) vẫn bế tắc cho ứng dụng của tôi, do thời gian chờ nội bộ và việc sử dụng cả StandardOutput và StandardError cho ứng dụng được sinh ra. Đây là những gì làm việc cho tôi:

Process p = new Process()
{
  StartInfo = new ProcessStartInfo()
  {
    FileName = exe,
    Arguments = args,
    UseShellExecute = false,
    RedirectStandardOutput = true,
    RedirectStandardError = true
  }
};
p.Start();

string cv_error = null;
Thread et = new Thread(() => { cv_error = p.StandardError.ReadToEnd(); });
et.Start();

string cv_out = null;
Thread ot = new Thread(() => { cv_out = p.StandardOutput.ReadToEnd(); });
ot.Start();

p.WaitForExit();
ot.Join();
et.Join();

Chỉnh sửa: đã thêm khởi tạo StartInfo vào mẫu mã


Đây là những gì tôi sử dụng và không bao giờ có vấn đề nữa với bế tắc.
RoTable

3

Tôi đã giải quyết nó theo cách này:

            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();

Tôi đã chuyển hướng cả đầu vào, đầu ra và lỗi và xử lý đọc từ các luồng đầu ra và lỗi. Giải pháp này hoạt động cho SDK 7- 8.1, cho cả Windows 7 và Windows 8


2
Elina: cảm ơn câu trả lời của bạn. Có một số ghi chú ở dưới cùng của tài liệu MSDN này ( msdn.microsoft.com/en-us/l Library / trộm ) cảnh báo về các bế tắc tiềm năng nếu bạn đọc đến cuối cả hai dòng stdout và stderr được chuyển hướng đồng bộ. Thật khó để biết liệu giải pháp của bạn có dễ bị vấn đề này hay không. Ngoài ra, có vẻ như bạn đang gửi đầu ra stdout / stderr của quá trình trở lại làm đầu vào. Tại sao? :)
Matthew Piatt

3

Tôi đã cố gắng tạo một lớp sẽ giải quyết vấn đề của bạn bằng cách đọc luồng không đồng bộ, bằng cách tính đến các câu trả lời Mark Byers, Rob, stevejay. Làm như vậy tôi nhận ra rằng có một lỗi liên quan đến quá trình đọc luồng đầu ra không đồng bộ.

Tôi đã báo cáo lỗi đó tại Microsoft: https://connect.microsoft.com/VisualStudio/feedback/details/3119134

Tóm lược:

Bạn không thể làm điều đó:

process.BeginOutputReadLine (); process.Start ();

Bạn sẽ nhận được System.InvalidOperationException: StandardOut chưa được chuyển hướng hoặc quá trình chưa bắt đầu.

================================================== ================================================== ========================

Sau đó, bạn phải bắt đầu đọc đầu ra không đồng bộ sau khi quá trình được bắt đầu:

process.Start (); process.BeginOutputReadLine ();

Làm như vậy, tạo điều kiện cuộc đua vì luồng đầu ra có thể nhận dữ liệu trước khi bạn đặt nó thành không đồng bộ:

process.Start(); 
// Here the operating system could give the cpu to another thread.  
// For example, the newly created thread (Process) and it could start writing to the output
// immediately before next line would execute. 
// That create a race condition.
process.BeginOutputReadLine();

================================================== ================================================== ========================

Sau đó, một số người có thể nói rằng bạn chỉ cần đọc luồng trước khi bạn đặt nó thành không đồng bộ. Nhưng vấn đề tương tự xảy ra. Sẽ có một điều kiện chạy đua giữa đọc đồng bộ và đặt luồng thành chế độ không đồng bộ.

================================================== ================================================== ========================

Không có cách nào để đạt được đọc không đồng bộ an toàn một luồng đầu ra của một quy trình theo cách thực tế "Quy trình" và "ProcessStartInfo" đã được thiết kế.

Bạn có thể tốt hơn bằng cách sử dụng đọc không đồng bộ như được đề xuất bởi người dùng khác cho trường hợp của bạn. Nhưng bạn nên lưu ý rằng bạn có thể bỏ lỡ một số thông tin do điều kiện cuộc đua.


1

Tôi nghĩ rằng đây là cách tiếp cận đơn giản và tốt hơn (chúng tôi không cần 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;
    }
}

Đúng, nhưng bạn không nên làm .FileName = Path + @"\ggsci.exe" + @" < obeycommand.txt"để đơn giản hóa mã của mình? Hoặc có thể một cái gì đó tương đương với "echo command | " + Path + @"\ggsci.exe"nếu bạn thực sự không muốn sử dụng tệp obeycommand.txt riêng.
Amit N Nikol

3
Giải pháp của bạn không cần AutoResetEvent nhưng bạn thăm dò ý kiến. Khi bạn thực hiện thăm dò thay vì sử dụng sự kiện (khi chúng có sẵn) thì bạn đang sử dụng CPU mà không có lý do và điều đó cho thấy rằng bạn là một lập trình viên tồi. Giải pháp của bạn thực sự tồi tệ khi so sánh với giải pháp khác bằng AutoResetEvent. (Nhưng tôi đã không cho bạn -1 vì bạn đã cố gắng giúp đỡ!).
Eric Ouellet

1

Không có câu trả lời ở trên là làm công việc.

Giải pháp Rob bị treo và giải pháp 'Mark Byers' có được ngoại lệ được xử lý. (Tôi đã thử "giải pháp" của các câu trả lời khác).

Vì vậy, tôi quyết định đề xuất một giải pháp khác:

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);
}

Mã này được gỡ lỗi và hoạt động hoàn hảo.


1
Tốt chỉ lưu ý rằng tham số mã thông báo không được cung cấp khi gọi GetProcessOutputWithTimeoutphương thức.
S.Serpooshan

1

Giới thiệu

Hiện tại câu trả lời được chấp nhận không hoạt động (ném ngoại lệ) và có quá nhiều cách giải quyết nhưng không có mã hoàn chỉnh. Điều này rõ ràng là lãng phí rất nhiều thời gian của mọi người vì đây là một câu hỏi phổ biến.

Kết hợp câu trả lời của Mark Byers và câu trả lời của Karol Tyl, tôi đã viết mã đầy đủ dựa trên cách tôi muốn sử dụng phương thức Process.Start.

Sử dụng

Tôi đã sử dụng nó để tạo hộp thoại tiến trình xung quanh các lệnh git. Đây là cách tôi đã sử dụng nó:

    private bool Run(string fullCommand)
    {
        Error = "";
        int timeout = 5000;

        var result = ProcessNoBS.Start(
            filename: @"C:\Program Files\Git\cmd\git.exe",
            arguments: fullCommand,
            timeoutInMs: timeout,
            workingDir: @"C:\test");

        if (result.hasTimedOut)
        {
            Error = String.Format("Timeout ({0} sec)", timeout/1000);
            return false;
        }

        if (result.ExitCode != 0)
        {
            Error = (String.IsNullOrWhiteSpace(result.stderr)) 
                ? result.stdout : result.stderr;
            return false;
        }

        return true;
    }

Về lý thuyết, bạn cũng có thể kết hợp thiết bị xuất chuẩn và thiết bị xuất chuẩn, nhưng tôi chưa thử nghiệm điều đó.

public struct ProcessResult
{
    public string stdout;
    public string stderr;
    public bool hasTimedOut;
    private int? exitCode;

    public ProcessResult(bool hasTimedOut = true)
    {
        this.hasTimedOut = hasTimedOut;
        stdout = null;
        stderr = null;
        exitCode = null;
    }

    public int ExitCode
    {
        get 
        {
            if (hasTimedOut)
                throw new InvalidOperationException(
                    "There was no exit code - process has timed out.");

            return (int)exitCode;
        }
        set
        {
            exitCode = value;
        }
    }
}

public class ProcessNoBS
{
    public static ProcessResult Start(string filename, string arguments,
        string workingDir = null, int timeoutInMs = 5000,
        bool combineStdoutAndStderr = false)
    {
        using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false))
        using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false))
        {
            using (var process = new Process())
            {
                var info = new ProcessStartInfo();

                info.CreateNoWindow = true;
                info.FileName = filename;
                info.Arguments = arguments;
                info.UseShellExecute = false;
                info.RedirectStandardOutput = true;
                info.RedirectStandardError = true;

                if (workingDir != null)
                    info.WorkingDirectory = workingDir;

                process.StartInfo = info;

                StringBuilder stdout = new StringBuilder();
                StringBuilder stderr = combineStdoutAndStderr
                    ? stdout : new StringBuilder();

                var result = new ProcessResult();

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

                    process.Start();

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

                    if (process.WaitForExit(timeoutInMs))
                        result.ExitCode = process.ExitCode;
                    // else process has timed out 
                    // but that's already default ProcessResult

                    result.stdout = stdout.ToString();
                    if (combineStdoutAndStderr)
                        result.stderr = null;
                    else
                        result.stderr = stderr.ToString();

                    return result;
                }
                finally
                {
                    outputWaitHandle.WaitOne(timeoutInMs);
                    errorWaitHandle.WaitOne(timeoutInMs);
                }
            }
        }
    }
}

Vẫn nhận System.ObjectDisposedException: Xử lý an toàn cũng đã bị đóng trên phiên bản này.
Matt

1

Tôi biết rằng điều này là siêu cũ nhưng, sau khi đọc toàn bộ trang này, không có giải pháp nào phù hợp với tôi, mặc dù tôi đã không thử Muhammad Rehan vì mã này hơi khó theo dõi, mặc dù tôi đoán rằng anh ta đang đi đúng hướng . Khi tôi nói nó không hoạt động không hoàn toàn đúng, đôi khi nó sẽ hoạt động tốt, tôi đoán đó là việc cần làm với độ dài của đầu ra trước dấu EOF.

Dù sao, giải pháp hiệu quả với tôi là sử dụng các luồng khác nhau để đọc StandardOutput và StandardError và viết tin nhắn.

        StreamWriter sw = null;
        var queue = new ConcurrentQueue<string>();

        var flushTask = new System.Timers.Timer(50);
        flushTask.Elapsed += (s, e) =>
        {
            while (!queue.IsEmpty)
            {
                string line = null;
                if (queue.TryDequeue(out line))
                    sw.WriteLine(line);
            }
            sw.FlushAsync();
        };
        flushTask.Start();

        using (var process = new Process())
        {
            try
            {
                process.StartInfo.FileName = @"...";
                process.StartInfo.Arguments = $"...";
                process.StartInfo.UseShellExecute = false;
                process.StartInfo.RedirectStandardOutput = true;
                process.StartInfo.RedirectStandardError = true;

                process.Start();

                var outputRead = Task.Run(() =>
                {
                    while (!process.StandardOutput.EndOfStream)
                    {
                        queue.Enqueue(process.StandardOutput.ReadLine());
                    }
                });

                var errorRead = Task.Run(() =>
                {
                    while (!process.StandardError.EndOfStream)
                    {
                        queue.Enqueue(process.StandardError.ReadLine());
                    }
                });

                var timeout = new TimeSpan(hours: 0, minutes: 10, seconds: 0);

                if (Task.WaitAll(new[] { outputRead, errorRead }, timeout) &&
                    process.WaitForExit((int)timeout.TotalMilliseconds))
                {
                    if (process.ExitCode != 0)
                    {
                        throw new Exception($"Failed run... blah blah");
                    }
                }
                else
                {
                    throw new Exception($"process timed out after waiting {timeout}");
                }
            }
            catch (Exception e)
            {
                throw new Exception($"Failed to succesfully run the process.....", e);
            }
        }
    }

Hy vọng điều này sẽ giúp được ai đó, người nghĩ rằng điều này có thể rất khó!


Ngoại lệ: sw.FlushAsync(): Object is not set to an instance of an object. sw is null. Làm thế nào / nên swđược xác định ở đâu?
wallyk

1

Sau khi đọc tất cả các bài viết ở đây, tôi giải quyết về giải pháp hợp nhất của Marko Avlijaš. Tuy nhiên , nó không giải quyết được tất cả các vấn đề của tôi.

Trong môi trường của chúng tôi, chúng tôi có một Dịch vụ Windows được lên kế hoạch để chạy hàng trăm tệp .bat .cmd .exe, ... vv đã tích lũy qua nhiều năm và được viết bởi nhiều người khác nhau và theo các phong cách khác nhau. Chúng tôi không kiểm soát việc viết chương trình & kịch bản, chúng tôi chỉ chịu trách nhiệm lên lịch, chạy và báo cáo về thành công / thất bại.

Vì vậy, tôi đã thử khá nhiều tất cả các đề xuất ở đây với các mức độ thành công khác nhau. Câu trả lời của Marko gần như hoàn hảo, nhưng khi hoạt động như một dịch vụ, nó không phải lúc nào cũng bắt được thiết bị xuất chuẩn. Tôi không bao giờ đi đến tận cùng của lý do tại sao không.

Giải pháp duy nhất mà chúng tôi tìm thấy hoạt động trong TẤT CẢ các trường hợp của chúng tôi là: http://csharptest.net/319/USE-the- Processrunner- class / index.html


Tôi sẽ thử thư viện này. Tôi đã phân tích mã, và có vẻ như nó đang sử dụng các đại biểu một cách hợp lý. Nó được đóng gói độc đáo trong Nuget. Về cơ bản nó bốc mùi của sự chuyên nghiệp, điều mà tôi không bao giờ có thể bị buộc tội. Nếu nó cắn, sẽ bảo.
Steve Hibbert

Liên kết đến mã nguồn đã chết. Xin vui lòng lần sau sao chép mã để trả lời.
Vitaly Zdanevich

1

Cách giải quyết tôi đã kết thúc bằng cách sử dụng để tránh tất cả sự phức tạp:

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

Vì vậy, tôi tạo một tệp tạm thời, chuyển hướng cả đầu ra và lỗi cho nó bằng cách sử dụng > outputfile > 2>&1và sau đó chỉ đọc tệp sau khi quá trình kết thúc.

Các giải pháp khác là tốt cho các kịch bản mà bạn muốn làm các công cụ khác với đầu ra, nhưng đối với các công cụ đơn giản, điều này tránh được rất nhiều sự phức tạp.


1

Tôi đã đọc nhiều câu trả lời và tự làm. Không chắc cái này sẽ sửa trong mọi trường hợp, nhưng nó sửa trong môi trường của tôi. Tôi chỉ không sử dụng WaitForExit và sử dụng WaitHandle.WaitAll trên cả tín hiệu kết thúc đầu ra và lỗi. Tôi sẽ rất vui, nếu ai đó sẽ thấy những vấn đề có thể xảy ra với điều đó. Hoặc nếu nó sẽ giúp ai đó. Đối với tôi nó tốt hơn vì không sử dụng thời gian chờ.

private static int DoProcess(string workingDir, string fileName, string arguments)
{
    int exitCode;
    using (var process = new Process
    {
        StartInfo =
        {
            WorkingDirectory = workingDir,
            WindowStyle = ProcessWindowStyle.Hidden,
            CreateNoWindow = true,
            UseShellExecute = false,
            FileName = fileName,
            Arguments = arguments,
            RedirectStandardError = true,
            RedirectStandardOutput = true
        },
        EnableRaisingEvents = true
    })
    {
        using (var outputWaitHandle = new AutoResetEvent(false))
        using (var errorWaitHandle = new AutoResetEvent(false))
        {
            process.OutputDataReceived += (sender, args) =>
            {
                // ReSharper disable once AccessToDisposedClosure
                if (args.Data != null) Debug.Log(args.Data);
                else outputWaitHandle.Set();
            };
            process.ErrorDataReceived += (sender, args) =>
            {
                // ReSharper disable once AccessToDisposedClosure
                if (args.Data != null) Debug.LogError(args.Data);
                else errorWaitHandle.Set();
            };

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

            WaitHandle.WaitAll(new WaitHandle[] { outputWaitHandle, errorWaitHandle });

            exitCode = process.ExitCode;
        }
    }
    return exitCode;
}

Tôi đã sử dụng cái này và kết hợp với Task.Run để xử lý thời gian chờ, tôi cũng quay lại processid để giết khi hết thời gian
plus5volt

0

Tôi nghĩ với async, có thể có một giải pháp thanh lịch hơn và không gặp bế tắc ngay cả khi sử dụng cả StandardOutput và standardError:

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;

    process.Start();

    var tStandardOutput = process.StandardOutput.ReadToEndAsync();
    var tStandardError = process.StandardError.ReadToEndAsync();

    if (process.WaitForExit(timeout))
    {
        string output = await tStandardOutput;
        string errors = await tStandardError;

        // Process completed. Check process.ExitCode here.
    }
    else
    {
        // Timed out.
    }
}

Nó dựa trên câu trả lời của Mark Byers. Nếu bạn không ở trong phương thức không đồng bộ, bạn có thể sử dụng string output = tStandardOutput.result;thay vìawait



-1

Bài đăng này có thể lỗi thời nhưng tôi đã tìm ra nguyên nhân chính tại sao nó thường bị treo là do tràn ngăn xếp cho redirectSt Chuẩnoutput hoặc nếu bạn có redirectSt Chuẩnerror.

Khi dữ liệu đầu ra hoặc dữ liệu lỗi lớn, nó sẽ gây ra thời gian treo vì nó vẫn đang xử lý trong thời gian không xác định.

để giải quyết vấn đề này:

p.StartInfo.RedirectStandardoutput = False
p.StartInfo.RedirectStandarderror = False

11
Vấn đề là mọi người rõ ràng đặt chúng thành đúng vì họ muốn có thể truy cập các luồng đó! Khác thực sự chúng ta chỉ có thể để chúng sai.
dùng276648

-1

Hãy để chúng tôi gọi mã mẫu được đăng ở đây là bộ chuyển hướng và chương trình khác được chuyển hướng. Nếu là tôi thì có lẽ tôi sẽ viết một chương trình chuyển hướng thử nghiệm có thể được sử dụng để nhân đôi vấn đề.

Tôi cũng vậy. Đối với dữ liệu thử nghiệm, tôi đã sử dụng ECMA-334 C # Language Đặc tả PDF; đó là khoảng 5 MB. Sau đây là phần quan trọng của điều đó.

StreamReader stream = null;
try { stream = new StreamReader(Path); }
catch (Exception ex)
{
    Console.Error.WriteLine("Input open error: " + ex.Message);
    return;
}
Console.SetIn(stream);
int datasize = 0;
try
{
    string record = Console.ReadLine();
    while (record != null)
    {
        datasize += record.Length + 2;
        record = Console.ReadLine();
        Console.WriteLine(record);
    }
}
catch (Exception ex)
{
    Console.Error.WriteLine($"Error: {ex.Message}");
    return;
}

Giá trị dữ liệu hóa không khớp với kích thước tệp thực tế nhưng điều đó không quan trọng. Không rõ liệu một tệp PDF luôn sử dụng cả CR và LF ở cuối dòng hay không nhưng điều đó không quan trọng đối với điều này. Bạn có thể sử dụng bất kỳ tệp văn bản lớn khác để kiểm tra.

Sử dụng mã chuyển hướng mẫu bị treo khi tôi ghi lượng dữ liệu lớn nhưng không phải khi tôi viết một lượng nhỏ.

Tôi đã cố gắng rất nhiều để bằng cách nào đó theo dõi việc thực thi mã đó và tôi không thể. Tôi đã nhận xét các dòng của chương trình được chuyển hướng đã vô hiệu hóa việc tạo giao diện điều khiển cho chương trình được chuyển hướng để cố gắng có được một cửa sổ giao diện điều khiển riêng nhưng tôi không thể.

Sau đó, tôi tìm thấy Cách khởi động ứng dụng bảng điều khiển trong một cửa sổ mới, cửa sổ của cha mẹ hoặc không có cửa sổ . Vì vậy, rõ ràng chúng ta không thể (dễ dàng) có một bàn điều khiển riêng khi một chương trình bàn điều khiển khởi động một chương trình bàn điều khiển khác mà không có ShellExecute và vì ShellExecute không hỗ trợ chuyển hướng, chúng ta phải chia sẻ một bàn điều khiển, ngay cả khi chúng ta chỉ định không có cửa sổ cho quy trình khác.

Tôi giả sử rằng nếu chương trình được chuyển hướng lấp đầy bộ đệm ở đâu đó thì nó phải chờ dữ liệu được đọc và nếu tại thời điểm đó không có dữ liệu nào được đọc bởi bộ chuyển hướng thì đó là một bế tắc.

Giải pháp là không sử dụng ReadToEnd và đọc dữ liệu trong khi dữ liệu đang được ghi nhưng không cần thiết phải sử dụng đọc không đồng bộ. Giải pháp có thể khá đơn giản. Các công việc sau đây đối với tôi với PDF 5 MB.

ProcessStartInfo info = new ProcessStartInfo(TheProgram);
info.CreateNoWindow = true;
info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
info.RedirectStandardOutput = true;
info.UseShellExecute = false;
Process p = Process.Start(info);
string record = p.StandardOutput.ReadLine();
while (record != null)
{
    Console.WriteLine(record);
    record = p.StandardOutput.ReadLine();
}
p.WaitForExit();

Một khả năng khác là sử dụng chương trình GUI để thực hiện chuyển hướng. Mã trước hoạt động trong một ứng dụng WPF trừ khi có sửa đổi rõ ràng.


-3

Tôi đã có cùng một vấn đề, nhưng lý do là khác nhau. Tuy nhiên, điều đó sẽ xảy ra trong Windows 8, nhưng không phải trong Windows 7. Dòng sau đây dường như đã gây ra sự cố.

pProcess.StartInfo.UseShellExecute = False

Giải pháp là KHÔNG vô hiệu hóa UseShellExecute. Bây giờ tôi đã nhận được một cửa sổ bật lên Shell, điều này là không mong muốn, nhưng tốt hơn nhiều so với chương trình chờ đợi không có gì cụ thể xảy ra. Vì vậy, tôi đã thêm các công việc sau đây cho việc đó:

pProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden

Bây giờ điều duy nhất làm phiền tôi là tại sao điều này lại xảy ra trong Windows 8 ngay từ đầu.


1
Bạn cần UseShellExecuteđược đặt thành false nếu bạn muốn chuyển hướng đầu ra.
Brad Moore
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.