Получить прямой вывод из процесса - PullRequest
19 голосов
/ 10 января 2012

У меня проблема в моем проекте.Я хотел бы запустить процесс, 7z.exe (консольная версия).Я пробовал три разные вещи:

  • Process.StandardOutput.ReadToEnd ();
  • OutputDataReceived & BeginOutputReadLine
  • StreamWriter

Ничего не работаетОн всегда «ждет» окончания процесса, чтобы показать, чего я хочу.У меня нет кода для размещения, просто если вы хотите, чтобы мой код с одной из вещей, перечисленных выше.Спасибо.

Редактировать: Мой код:

        process.StartInfo.UseShellExecute = false;
        process.StartInfo.RedirectStandardOutput = true;
        process.StartInfo.CreateNoWindow = true;
        process.Start();

        this.sr = process.StandardOutput;
        while (!sr.EndOfStream)
        {
            String s = sr.ReadLine();
            if (s != "")
            {
                System.Console.WriteLine(DateTime.Now + " - " + s);
            }
        }

Или

process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.OutputDataReceived += new DataReceivedEventHandler(recieve);
process.StartInfo.CreateNoWindow = true;
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
public void recieve(object e, DataReceivedEventArgs outLine)
{
    System.Console.WriteLine(DateTime.Now + " - " + outLine.Data);
}

Или

process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.Start();
string output = p.StandardOutput.ReadToEnd();
process.WaitForExit();

Где «процесс» - это мой предварительныйmade Process

Хорошо, я знаю, почему он не работает должным образом: ошибка 7z.exe: он отображает процент загрузки в консоли и отправляет информацию только после завершения работы текущего файла.В извлечении, например, работает нормально :).Я буду искать другой способ использования функций 7z без 7z.exe (возможно, с 7za.exe или с некоторыми DLL).Спасибо всем.Чтобы ответить на вопрос, событие OuputDataRectained работает отлично!

Ответы [ 6 ]

20 голосов
/ 10 января 2012

Взгляните на эту страницу, похоже, это решение для вас: http://msdn.microsoft.com/en-us/library/system.diagnostics.process.beginoutputreadline.aspx и http://msdn.microsoft.com/en-us/library/system.diagnostics.process.standardoutput.aspx

[Изменить] Это рабочий пример:

        Process p = new Process();
        p.StartInfo.RedirectStandardError = true;
        p.StartInfo.RedirectStandardOutput = true;
        p.StartInfo.UseShellExecute = false;
        p.StartInfo.CreateNoWindow = true;
        p.StartInfo.FileName = @"C:\Program Files (x86)\gnuwin32\bin\ls.exe";
        p.StartInfo.Arguments = "-R C:\\";

        p.OutputDataReceived += new DataReceivedEventHandler((s, e) => 
        { 
            Console.WriteLine(e.Data); 
        });
        p.ErrorDataReceived += new DataReceivedEventHandler((s, e) =>
        {
            Console.WriteLine(e.Data);
        });

        p.Start();
        p.BeginOutputReadLine();
        p.BeginErrorReadLine();

Кстати, ls -R C: \ перечисляет все файлы из корня C: рекурсивно. Это много файлов, и я уверен, что это не будет сделано, когда первые результаты появятся на экране. Существует вероятность того, что 7zip задержит вывод перед его отображением. Я не уверен, какие параметры вы даете процессу.

5 голосов
/ 26 июня 2015

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

Пример использования:

        Process p = new Process(...);

        p.StartInfo.UseShellExecute = false;
        p.StartInfo.RedirectStandardOutput = true;
        p.StartInfo.RedirectStandardError = true;
        p.StartInfo.RedirectStandardInput = true; // Is a MUST!
        p.EnableRaisingEvents = true;

        p.OutputDataReceived += OutputDataReceived;
        p.ErrorDataReceived += ErrorDataReceived;

        Process.Start();

        p.BeginOutputReadLine();
        p.BeginErrorReadLine();

        p.WaitForExit();

        p.OutputDataReceived -= OutputDataReceived;
        p.ErrorDataReceived -= ErrorDataReceived;

...

    void OutputDataReceived(object sender, DataReceivedEventArgs e)
    {
        // Process line provided in e.Data
    }

    void ErrorDataReceived(object sender, DataReceivedEventArgs e)
    {
        // Process line provided in e.Data
    }
5 голосов
/ 09 мая 2014

Я не знаю, ищет ли кто-нибудь еще решение для этого, но оно несколько раз подходило для меня, потому что я пишу инструмент в Unity для поддержки некоторых игр и из-за ограниченной функциональной совместимости некоторых В системах с моно (например, PIA для чтения текста из Word) мне часто приходится писать исполняемые файлы для конкретной ОС (иногда для Windows, иногда для MacOS) и запускать их из Process.Start ().

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

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

В любом случае, вот решение, которое в настоящее время работает для меня:

В основном или вызывающем приложении я делаю что-то вроде этого:

/// <summary>
/// Handles the OK button click.
/// </summary>
private void HandleOKButtonClick() {
string executableFolder = "";

#if UNITY_EDITOR
executableFolder = Path.Combine(Application.dataPath, "../../../../build/Include/Executables");
#else
executableFolder = Path.Combine(Application.dataPath, "Include/Executables");
#endif

EstablishSocketServer();

var proc = new Process {
    StartInfo = new ProcessStartInfo {
        FileName = Path.Combine(executableFolder, "WordConverter.exe"),
        Arguments = locationField.value + " " + _ipAddress.ToString() + " " + SOCKET_PORT.ToString(), 
        UseShellExecute = false,
        RedirectStandardOutput = true,
        CreateNoWindow = true
    }
};

proc.Start();

Вот где я устанавливаю сервер сокетов:

/// <summary>
/// Establishes a socket server for communication with each chapter build script so we can get progress updates.
/// </summary>
private void EstablishSocketServer() {
    //_dialog.SetMessage("Establishing socket connection for updates. \n");
    TearDownSocketServer();

    Thread currentThread;

    _ipAddress = Dns.GetHostEntry(Dns.GetHostName()).AddressList[0];
    _listener = new TcpListener(_ipAddress, SOCKET_PORT);
    _listener.Start();

    UnityEngine.Debug.Log("Server mounted, listening to port " + SOCKET_PORT);

    _builderCommThreads = new List<Thread>();

    for (int i = 0; i < 1; i++) {
        currentThread = new Thread(new ThreadStart(HandleIncomingSocketMessage));
        _builderCommThreads.Add(currentThread);
        currentThread.Start();
    }
}

/// <summary>
/// Tears down socket server.
/// </summary>
private void TearDownSocketServer() {
    _builderCommThreads = null;

    _ipAddress = null;
    _listener = null;
}

Вот мой обработчик сокета для потока ... обратите внимание, что в некоторых случаях вам придется создавать несколько потоков; вот почему у меня есть этот _builderCommThreads List (я перенес его из кода в другом месте, где я делал нечто подобное, но вызывал несколько экземпляров подряд):

/// <summary>
/// Handles the incoming socket message.
/// </summary>
private void HandleIncomingSocketMessage() {
    if (_listener == null) return;

    while (true) {
        Socket soc = _listener.AcceptSocket();
        //soc.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 10000);
        NetworkStream s = null;
        StreamReader sr = null;
        StreamWriter sw = null;
        bool reading = true;

        if (soc == null) break;

        UnityEngine.Debug.Log("Connected: " + soc.RemoteEndPoint);

        try {
            s = new NetworkStream(soc);
            sr = new StreamReader(s, Encoding.Unicode);
            sw = new StreamWriter(s, Encoding.Unicode);
            sw.AutoFlush = true; // enable automatic flushing

            while (reading == true) {
                string line = sr.ReadLine();

                if (line != null) {
                    //UnityEngine.Debug.Log("SOCKET MESSAGE: " + line);
                    UnityEngine.Debug.Log(line);

                    lock (_threadLock) {
                        // Do stuff with your messages here
                    }
                }
            }

            //
        } catch (Exception e) {
            if (s != null) s.Close();
            if (soc != null) soc.Close();
            UnityEngine.Debug.Log(e.Message);
            //return;
        } finally {

        //
        if (s != null) s.Close();
        if (soc != null) soc.Close();

        UnityEngine.Debug.Log("Disconnected: " + soc.RemoteEndPoint);
        }
    }

    return;
}

Конечно, вам нужно объявить некоторые вещи вверху:

private TcpListener _listener = null;
private IPAddress _ipAddress = null;
private List<Thread> _builderCommThreads = null;
private System.Object _threadLock = new System.Object();

... затем в вызываемом исполняемом файле установите другой конец (я использовал статику в этом случае, вы можете использовать все, что захотите):

private static TcpClient _client = null;
private static Stream _s = null;
private static StreamReader _sr = null;
private static StreamWriter _sw = null;
private static string _ipAddress = "";
private static int _port = 0;
private static System.Object _threadLock = new System.Object();

/// <summary>
/// Main method.
/// </summary>
/// <param name="args"></param>
static void Main(string[] args) {
    try {
        if (args.Length == 3) {
            _ipAddress = args[1];
            _port = Convert.ToInt32(args[2]);

            EstablishSocketClient();
        }

        // Do stuff here

        if (args.Length == 3) Cleanup();
    } catch (Exception exception) {
        // Handle stuff here
        if (args.Length == 3) Cleanup();
    }
}

/// <summary>
/// Establishes the socket client.
/// </summary>
private static void EstablishSocketClient() {
    _client = new TcpClient(_ipAddress, _port);

    try {
        _s = _client.GetStream();
        _sr = new StreamReader(_s, Encoding.Unicode);
        _sw = new StreamWriter(_s, Encoding.Unicode);
        _sw.AutoFlush = true;
    } catch (Exception e) {
        Cleanup();
    }
}

/// <summary>
/// Clean up this instance.
/// </summary>
private static void Cleanup() {
    _s.Close();
    _client.Close();

    _client = null;
    _s = null;
    _sr = null;
    _sw = null;
}

/// <summary>
/// Logs a message for output.
/// </summary>
/// <param name="message"></param>
private static void Log(string message) {
    if (_sw != null) {
        _sw.WriteLine(message);
    } else {
        Console.Out.WriteLine(message);
    }
}

... Я использую это для запуска инструмента командной строки в Windows, который использует PIA для извлечения текста из документа Word. Я пробовал PIA .dll в Unity, но столкнулся с проблемами взаимодействия с моно. Я также использую его в MacOS для вызова сценариев оболочки, которые запускают дополнительные экземпляры Unity в пакетном режиме, и запускают сценарии редактора в тех экземплярах, которые обращаются к инструменту через это соединение через сокет. Это здорово, потому что теперь я могу отправлять отзывы пользователю, отлаживать, отслеживать и отвечать на конкретные шаги процесса, и так далее, и так далее.

НТН

1 голос
/ 11 января 2012

Я использовал класс CmdProcessor, описанный здесь , в нескольких проектах с большим успехом. Сначала это выглядит немного устрашающе, но очень просто в использовании.

0 голосов
/ 10 июля 2019

Проблема вызвана вызовом Process.WaitForExit метода .В соответствии с документацией это делается следующим образом:

Устанавливает период времени ожидания завершения ассоциированного процесса, а блокирует текущий поток выполнения до тех пор, покаистек или процесс завершен. Чтобы избежать блокировки текущего потока, используйте событие Exited .

Итак, чтобы предотвратить блокировку потока до завершения процесса, подключите процесс .Exited обработчик события объекта Process, как показано ниже.Событие Exited может возникнуть, только если значение свойства EnableRaisingEvents равно true.

    process.EnableRaisingEvents = true;
    process.Exited += Proc_Exited;


    private void Proc_Exited(object sender, EventArgs e)
    {
        // Code to handle process exit
    }

Сделав это, вы сможете получить выходные данные вашего процесса, пока он еще выполняется, через Process.OutputDataReceived событие , как вы делаете в настоящее время.(PS - пример кода на этой странице события также допускает ошибку при использовании Process.WaitForExit.)

Еще одно замечание: вам нужно убедиться, что ваш объект Process не очищен, прежде чем ваш метод Exited может сработать.Если ваш процесс инициализирован в операторе using, это может быть проблемой.

0 голосов
/ 10 января 2012

Попробуйте это.

        Process notePad = new Process();

        notePad.StartInfo.FileName = "7z.exe";
        notePad.StartInfo.RedirectStandardOutput = true;
        notePad.StartInfo.UseShellExecute = false;

        notePad.Start();
        StreamReader s = notePad.StandardOutput;



        String output= s.ReadToEnd();


        notePad.WaitForExit();

Пусть приведенное выше будет в thread.

Теперь для обновления вывода в пользовательский интерфейс вы можете использовать timer с двумя строками

  Console.Clear();
  Console.WriteLine(output);

Это может помочь вам

...