web-dev-qa-db-ja.com

プロセスからライブ出力を取得する

プロジェクトに問題があります。プロセス7z.exe(コンソールバージョン)を起動したいと思います。私は3つのことを試しました:

  • Process.StandardOutput.ReadToEnd();
  • OutputDataReceivedおよびBeginOutputReadLine
  • StreamWriter

何も動作しません。プロセスの終わりが私が欲しいものを示すのを常に「待ちます」。上にリストされているものの1つを含むコードが必要な場合にのみ、配置するコードはありません。ありがとう。

編集:私のコード:

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

「プロセス」は私の既製のプロセスです

OK、それが正しく機能しない理由を知っています。7z.exeがバグです。コンソールにロードの割合が表示され、現在のファイルが終了したときにのみ情報が送信されます。たとえば抽出では、それはうまくいきます:)。 7z.exeなしで7z関数を使用する別の方法を検索します(7za.exeまたはDLLを使用している可能性があります)。ありがとうございます。質問に答えるために、OuputDataRecievedイベントは正常に機能します!

21
Extaze

このページを見てください、これはあなたのためのソリューションです: http://msdn.Microsoft.com/en-us/library/system.diagnostics.process.beginoutputreadline.aspx and- 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が表示する前に出力を保持する可能性があります。プロセスにどのパラメータを指定するかわかりません。

24

出力やエラーのリダイレクトを正しく処理するには、入力もリダイレクトする必要があります。それはあなたが始めている外部アプリケーションのランタイムの機能/バグであるようで、私がこれまで見てきたことから、それはどこにも言及されていません。

使用例:

        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
    }
6
user3042599

誰かがまだこれに対する解決策を探しているかどうかはわかりませんが、いくつかのゲームをサポートするためにUnityでツールを作成しているため、特定のシステムとモノの相互運用性が制限されているため、何度か問題が発生しました(たとえば、Wordからテキストを読み取るためのPIAのように)OS固有(Windowsの場合もあり、MacOSの場合もある)の実行可能ファイルを作成して、Process.Start()から起動する必要があります。

問題は、このような実行可能ファイルを起動すると、メインアプリをブロックする別のスレッドで起動し、ハングアップすることです。この間に、それぞれのOSによって起動されたアイコンの回転を超えて、ユーザーに有用なフィードバックを提供したい場合は、ちょっと困惑しています。スレッドは実行が完了するまでブロックされているため、ストリームの使用は機能しません。

私が思いついた解決策は、一部の人にとっては極端に思えるかもしれませんが、私にとって非常にうまく機能することがわかっています。ソケットとマルチスレッドを使用して、2つのアプリ間に信頼できる同期通信をセットアップすることです。もちろん、これは両方のアプリを作成している場合にのみ機能します。そうでなければ、あなたは運が悪いと思います。 ...従来のストリームアプローチを使用したマルチスレッド化だけで機能するかどうかを確認したいので、誰かがそれを試して、ここに結果を投稿したい場合は、すばらしいでしょう。

とにかく、ここに私のために現在働いている解決策があります:

メインのアプリ、つまり通話アプリでは、次のようにします。

/// <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リストをそこに持っている理由です(同じようなことをしているが、続けて複数のインスタンスを呼び出すコードから他の場所に移植しました)。

/// <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ドキュメントからテキストを引き出します。 Unityで.dllをPIAで試しましたが、monoで相互運用性の問題が発生しました。 MacOSでもこれを使用して、追加のUnityインスタンスをバッチモードで起動するシェルスクリプトを呼び出し、このソケット接続を介してツールとやり取りするインスタンスでエディタースクリプトを実行しています。ユーザーにフィードバックを送信し、デバッグ、監視し、プロセスの特定のステップなどに応答できるので、すばらしいです。

HTH

5
user2848240

いくつかのプロジェクトで説明されているCmdProcessorクラス here を使用して、多くの成功を収めています。最初は少し手ごわそうに見えますが、とても使いやすいです。

1
joebalt

これを試して。

        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に入れます。

出力をUIに更新するために、2行でtimerを使用できます

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

これはあなたを助けるかもしれません

0
sonu thomas

この問題は Process.WaitForExit method を呼び出すことで発生します。ドキュメントによると、これは次のとおりです。

関連するプロセスが終了するまで待機する時間を設定し、時間が経過するかプロセスが終了するまで、実行の現在のスレッドをブロックします。 現在のスレッドのブロックを回避するには、Exitedイベントを使用します

したがって、プロセスが終了するまでスレッドがブロックされないようにするには、以下のように、Processオブジェクトの Process.Exited event ハンドラーを接続します。 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 event を介して、実行中のプロセスの出力を取得できます。 (PS-そのイベントページのコード例も、Process.WaitForExitの使用を間違えています。)

もう1つの注意点は、Exitedメソッドを起動する前に、Processオブジェクトがクリーンアップされないようにする必要があることです。 usingステートメントでプロセスが初期化されている場合、これが問題になる可能性があります。

0
Bork Blatt