Подтвердить что ты не робот

StandardOutput.ReadToEnd() зависает

У меня есть программа, которая часто использует внешнюю программу и считывает ее выходы. Он работает очень хорошо, используя ваш обычный процесс перенаправления вывода, но один конкретный аргумент по какой-то причине зависает, когда я пытаюсь его прочитать, никакое сообщение об ошибке - исключение, оно просто "останавливается", когда оно достигает этой строки. Я, конечно, использую централизованную функцию для вызова и чтения вывода из программы, которая заключается в следующем:

public string ADBShell(string adbInput)
{
    try
    {
        //Create Empty values
        string result = string.Empty;
        string error = string.Empty;
        string output = string.Empty;
        System.Diagnostics.ProcessStartInfo procStartInfo 
            = new System.Diagnostics.ProcessStartInfo(toolPath + "adb.exe");

        procStartInfo.Arguments = adbInput;
        procStartInfo.RedirectStandardOutput = true;
        procStartInfo.RedirectStandardError = true;
        procStartInfo.UseShellExecute = false;
        procStartInfo.CreateNoWindow = true;
        procStartInfo.WorkingDirectory = toolPath;
        System.Diagnostics.Process proc = new System.Diagnostics.Process();
        proc.StartInfo = procStartInfo;
        proc.Start();
        // Get the output into a string
        proc.WaitForExit();
        result = proc.StandardOutput.ReadToEnd();
        error = proc.StandardError.ReadToEnd();  //Some ADB outputs use this
        if (result.Length > 1)
        {
            output += result;
        }
        if (error.Length > 1)
        {
            output += error;
        }
        Return output;
    }
    catch (Exception objException)
    {
        throw objException;
    }
}

Линия, которая висит, result = proc.StandardOutput.ReadToEnd();, но опять же, не каждый раз, только при отправке определенного аргумента ( "start-server" ). Все остальные аргументы работают очень хорошо - он считывает значение и возвращает его. Это также странно, как он висит. Он не замерзает или не дает ошибку или что-то еще, он просто прекращает обработку. Как будто это была команда "return", за исключением того, что она даже не возвращается к вызывающей функции, она просто останавливает все, пока все еще работает и работает интерфейс. Кто-нибудь испытал это раньше? Кто-нибудь знает, что я должен попробовать? Я предполагаю, что это что-то неожиданное в самом потоке, но есть ли способ, которым я могу обрабатывать/игнорировать это, чтобы он все равно читал?

4b9b3361

Ответ 1

Предлагаемые решения с BeginOutputReadLine() являются хорошим способом, но в таких ситуациях это неприменимо, потому что процесс (конечно, с использованием WaitForExit()) завершается раньше, чем выход async завершен полностью.

Итак, я попытался реализовать его синхронно и обнаружил, что решение использует метод Peek() из класса StreamReader. Я добавил проверку на Peek() > -1, чтобы убедиться, что это не конец потока, как описано в статья MSDN и наконец, он работает и перестает вилять!

Вот код:

var process = new Process();
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.WorkingDirectory = @"C:\test\";
process.StartInfo.FileName = "test.exe";
process.StartInfo.Arguments = "your arguments here";

process.Start();
var output = new List<string>();

while (process.StandardOutput.Peek() > -1)
{
    output.Add(process.StandardOutput.ReadLine());
}

while (process.StandardError.Peek() > -1)
{
    output.Add(process.StandardError.ReadLine());
}
process.WaitForExit();

Ответ 2

Проблема заключается в том, что вы используете синхронные методы ReadToEnd для потоков StandardOutput и StandardError. Это может привести к потенциальному тупику, который вы испытываете. Это даже описано в MSDN. Там описано решение. В основном это: использовать асинхронную версию BeginOutputReadLine для чтения данных потока StandardOutput:

p.BeginOutputReadLine();
string error = p.StandardError.ReadToEnd();
p.WaitForExit();

Реализация чтения Async с использованием BeginOutputReadLine см. в ProcessStartInfo, зависающем на "WaitForExit" ? Почему?

Ответ 3

У меня была такая же проблема взаимоблокировки. Этот фрагмент кода работал у меня.

        ProcessStartInfo startInfo = new ProcessStartInfo("cmd")
        {
            WindowStyle = ProcessWindowStyle.Hidden,
            UseShellExecute = false,
            RedirectStandardInput = true,
            RedirectStandardOutput = true,
            CreateNoWindow = true
        };

        Process process = new Process();
        process.StartInfo = startInfo;
        process.Start();
        process.StandardInput.WriteLine("echo hi");
        process.StandardInput.WriteLine("exit");
        var output = process.StandardOutput.ReadToEnd();
        process.Dispose();

Ответ 4

Что-то вроде:

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

process.OutputDataReceived += (sender, args) =>
                               {
                                    var outputData = args.Data;
                                    // ...
                                };
process.ErrorDataReceived += (sender, args) =>
                            {
                                var errorData = args.Data;
                                // ...
                            };
process.WaitForExit();

Ответ 5

У меня была такая же проблема, что ошибка просто висела.

На основании вашего ответа на Daniel Hilgarth я даже не пытался использовать эти коды, хотя я думаю, что они бы сработали для меня.

Так как я хочу иметь возможность сделать какой-то дополнительный результат, в конце концов, я решил, что сделаю это с обоими выходами, выполненными в фоновом потоке.

public static class RunCommands
{
    #region Outputs Property

    private static object _outputsLockObject;
    private static object OutputsLockObject
    { 
        get
        {
            if (_outputsLockObject == null)
                Interlocked.CompareExchange(ref _outputsLockObject, new object(), null);
            return _outputsLockObject;
        }
    }

    private static Dictionary<object, CommandOutput> _outputs;
    private static Dictionary<object, CommandOutput> Outputs
    {
        get
        {
            if (_outputs != null)
                return _outputs;

            lock (OutputsLockObject)
            {
                _outputs = new Dictionary<object, CommandOutput>();
            }
            return _outputs;
        }
    }

    #endregion

    public static string GetCommandOutputSimple(ProcessStartInfo info, bool returnErrorIfPopulated = true)
    {
        // Redirect the output stream of the child process.
        info.UseShellExecute = false;
        info.CreateNoWindow = true;
        info.RedirectStandardOutput = true;
        info.RedirectStandardError = true;
        var process = new Process();
        process.StartInfo = info;
        process.ErrorDataReceived += ErrorDataHandler;
        process.OutputDataReceived += OutputDataHandler;

        var output = new CommandOutput();
        Outputs.Add(process, output);

        process.Start();

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

        // Wait for the process to finish reading from error and output before it is finished
        process.WaitForExit();

        Outputs.Remove(process);

        if (returnErrorIfPopulated && (!String.IsNullOrWhiteSpace(output.Error)))
        {
            return output.Error.TrimEnd('\n');
        }

        return output.Output.TrimEnd('\n');
    }

    private static void ErrorDataHandler(object sendingProcess, DataReceivedEventArgs errLine)
    {
        if (errLine.Data == null)
            return;

        if (!Outputs.ContainsKey(sendingProcess))
            return;

        var commandOutput = Outputs[sendingProcess];

        commandOutput.Error = commandOutput.Error + errLine.Data + "\n";
    }

    private static void OutputDataHandler(object sendingProcess, DataReceivedEventArgs outputLine)
    {
        if (outputLine.Data == null)
            return;

        if (!Outputs.ContainsKey(sendingProcess))
            return;

        var commandOutput = Outputs[sendingProcess];

        commandOutput.Output = commandOutput.Output + outputLine.Data + "\n";
    }
}
public class CommandOutput
{
    public string Error { get; set; }
    public string Output { get; set; }

    public CommandOutput()
    {
        Error = "";
        Output = "";
    }
}

Это сработало для меня и позволило мне не использовать таймаут для чтения.

Ответ 6

Что-то элегантное и работающее для меня:

Process nslookup = new Process()
{
   StartInfo = new ProcessStartInfo("nslookup")
   {
      RedirectStandardInput = true,
      RedirectStandardOutput = true,
      UseShellExecute = false,
      CreateNoWindow = true,
      WindowStyle = ProcessWindowStyle.Hidden
   }
};

nslookup.Start();
nslookup.StandardInput.WriteLine("set type=srv");
nslookup.StandardInput.WriteLine("_ldap._tcp.domain.local"); 

nslookup.StandardInput.Flush();
nslookup.StandardInput.Close();

string output = nslookup.StandardOutput.ReadToEnd();

nslookup.WaitForExit();
nslookup.Close();

Этот ответ я нашел здесь, и трюк использует Flush() и Close() для стандартного ввода.

Ответ 7

Принятое решение для ответа не сработало для меня. Мне пришлось использовать задачи, чтобы избежать тупика:

//Code to start process here

String outputResult = GetStreamOutput(process.StandardOutput);
String errorResult = GetStreamOutput(process.StandardError);

process.WaitForExit();

С функцией GetStreamOutput следующим образом:

private string GetStreamOutput(StreamReader stream)
{
   //Read output in separate task to avoid deadlocks
   var outputReadTask = Task.Run(() => stream.ReadToEnd());

   return outputReadTask.Result;
}

Ответ 8

На всякий случай кто-то натыкается на этот вопрос, когда он хочет использовать Windows Forms и TextBox (или RichTextBox), чтобы отображать ошибки и выводит процесс в реальном времени (поскольку они записываются в process.StandardOutput/process.StandardError).

Вам нужно использовать OutputDataReceived()/ErrorDataReceived(), чтобы читать оба потока без взаимоблокировок, но, насколько мне известно, нет пути (насколько я знаю), чтобы избежать взаимоблокировок, даже ответа Федора, который теперь содержит ответ "Ответ", тег и самый любимый на сегодняшний день, не делает трюк для меня.

Однако, когда вы используете RichTextBox (или TextBox) для вывода данных, другая проблема, с которой вы сталкиваетесь, заключается в том, как фактически записывать данные в текстовое поле в режиме реального времени (после его поступления). Вы получаете доступ к данным внутри одного из фоновых потоков OutputDataReceived()/ErrorDataReceived(), и вы можете только AppendText() из основного потока.

То, что я впервые попытался сделать, вызвал process.Start() из фонового потока, а затем вызвал BeginInvoke() => AppendText() в OutputDataReceived()/ErrorDataReceived() потоки, тогда как основной поток был process.WaitForExit().

Однако это привело к замораживанию моей формы и, в конечном счете, к вечности. После нескольких дней попыток я закончил с приведенным ниже решением, которое, похоже, работает очень хорошо.

Вкратце, вам нужно добавить сообщения в параллельную коллекцию внутри потоков OutputDataReceived()/ErrorDataReceived(), в то время как основной поток должен постоянно пытаться извлекать сообщения из этой коллекции и добавлять их в текстовое поле:

            ProcessStartInfo startInfo
                = new ProcessStartInfo(File, mysqldumpCommand);

            process.StartInfo.FileName = File;
            process.StartInfo.Arguments = mysqldumpCommand;
            process.StartInfo.CreateNoWindow = true;
            process.StartInfo.UseShellExecute = false;
            process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
            process.StartInfo.RedirectStandardInput = false;
            process.StartInfo.RedirectStandardError = true;
            process.StartInfo.RedirectStandardOutput = true;
            process.StartInfo.StandardErrorEncoding = Encoding.UTF8;
            process.StartInfo.StandardOutputEncoding = Encoding.UTF8;
            process.EnableRaisingEvents = true;

            ConcurrentQueue<string> messages = new ConcurrentQueue<string>();

            process.ErrorDataReceived += (object se, DataReceivedEventArgs ar) =>
            {
                string data = ar.Data;
                if (!string.IsNullOrWhiteSpace(data))
                    messages.Enqueue(data);
            };
            process.OutputDataReceived += (object se, DataReceivedEventArgs ar) =>
            {
                string data = ar.Data;
                if (!string.IsNullOrWhiteSpace(data))
                    messages.Enqueue(data);
            };

            process.Start();
            process.BeginErrorReadLine();
            process.BeginOutputReadLine();
            while (!process.HasExited)
            {
                string data = null;
                if (messages.TryDequeue(out data))
                    UpdateOutputText(data, tbOutput);
                Thread.Sleep(5);
            }

            process.WaitForExit();

Единственным недостатком этого подхода является тот факт, что вы можете потерять сообщения в довольно редком случае, когда процесс начинает писать их между process.Start() и process.BeginErrorReadLine()/process.BeginOutputReadLine(), просто имейте это в виду. Единственный способ избежать этого - прочитать полные потоки и (или) получить доступ к ним только при завершении процесса.

Ответ 9

первый

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

второй

 // Do not perform a synchronous read to the end of both 
 // redirected streams.
 // string output = p.StandardOutput.ReadToEnd();
 // string error = p.StandardError.ReadToEnd();
 // p.WaitForExit();
 // Use asynchronous read operations on at least one of the streams.
 p.BeginOutputReadLine();
 string error = p.StandardError.ReadToEnd();
 p.WaitForExit();

Это от MSDN