2016-01-05 11 views
9

私は、子プロセスと通信する必要がある、Rustの小さなncursesアプリケーションを作っています。私はすでにCommon Lispで書かれたプロトタイプを持っています。 gif hereは私がしたいことをうまく表示するでしょう。 CLはそのような小さなツールに膨大なメモリを使用しているので、私は書き直してみようとしています。Rustでブロックせずに子プロセスの出力を読み取るにはどうすればよいですか?

私はRust before(または他の低レベル言語)を使用していませんし、サブプロセスと対話する方法を理解するのにいくつか問題があります。

私が現在やっていることは大体これです:

  1. プロセスを作成します。

    let mut program = match Command::new(command) 
        .args(arguments) 
        .stdin(Stdio::piped()) 
        .stdout(Stdio::piped()) 
        .stderr(Stdio::piped()) 
        .spawn() { 
         Ok(child) => child, 
         Err(_) => { 
          println!("Cannot run program '{}'.", command); 
          return; 
         }, 
        }; 
    
  2. を読み取って入力を処理し、無限(ユーザーが終了するまで)ループ、それを渡しますこのような出力を待ち受けて(画面に書き込む):

    fn listen_for_output(program: &mut Child, 
            output_viewer: &TextViewer) { 
        match program.stdout { 
         Some(ref mut out) => { 
          let mut buf_string = String::new(); 
          match out.read_to_string(&mut buf_string) { 
           Ok(_) => output_viewer.append_string(buf_string), 
           Err(_) => return, 
          }; 
         }, 
         None => return, 
        }; 
    } 
    

しかし、read_to_stringの呼び出しは、プロセスが終了するまでプログラムをブロックします。私が見ることができるからread_to_endreadもブロックするようです。私がすぐに終了するlsのようなものを実行しようとすると、それは動作しますが、pythonまたはsbclのように終了しないものでは、サブプロセスを手動で終了すると一度だけ続行されます。

編集:this answerに基づいて

、私はBufReaderを使用するようにコードを変更:

fn listen_for_output(program: &mut Child, 
        output_viewer: &TextViewer) { 
    match program.stdout.as_mut() { 
     Some(out) => { 
      let buf_reader = BufReader::new(out); 
      for line in buf_reader.lines() { 
       match line { 
        Ok(l) => { 
         output_viewer.append_string(l); 
        }, 
        Err(_) => return, 
       }; 
      } 
     }, 
     None => return, 
    } 
} 

問題は依然として同じまましかし。利用可能なすべての行が読み込まれ、ブロックされます。ツールはどのプログラムでも動作するはずなので、読み込みを試みる前に出力が終了する時を推測する方法はありません。 BufReaderのいずれかのタイムアウトを設定する方法はありません。

答えて

11

ストリームはデフォルトでブロックです。 TCP/IPストリーム、ファイルシステムストリーム、パイプストリームはすべてブロックされています。あなたがストリームにバイトのチャンクを与えるように指示すると、それは与えられたバイト数または何かが起こるまで停止して待ちます(interrupt、ストリームの終わり、エラー)。

オペレーティングシステムはデータを読み込みプロセスに返すことを望んでいるので、次の行を待ってからすぐに処理する必要がある場合は、Shepmasterによって提案された方法がUnable to pipe to or from spawned child process more than onceで動作します。 (理論的には、オペレーティングシステムがreadでより多くのデータを待つことを許可されているため、実際にはオペレーティングシステムは早期の「短い読み取り」を待っている方が好ましい)。

この単純なBufReaderベースのアプローチは、複数のストリーム(子プロセスのstdoutおよびstderrなど)または複数のプロセスを処理する必要がある場合に機能しなくなります。たとえば、を待機しているプロセスがブロックされている間に、子プロセスがstderrパイプを排水するのを待っているときに、BufReaderベースのアプローチがデッドロックする可能性があります。あなたはあなたのプログラムが無限に子プロセスに待ちたくないとき

同様に、あなたはBufReaderを使用することはできません。子がまだ動作している間にプログレスバーまたはタイマーを表示し、出力しない場合があります。

オペレーティングシステムがプロセスにデータを返すことを熱望していない場合(「完全読み取り」から「短い読み取り」を好む)、最後の行がいくつか印刷されるため、BufReaderベースのアプローチは使用できません子プロセスによって灰色のゾーンに終わるかもしれません:オペレーティングシステムはそれらを持っていますが、BufReaderのバッファを埋めるのに十分ではありません。

BufReaderは、Readインターフェイスでストリームとの処理が許可されているものに制限されています。基本ストリームよりもブロックすることはありません。効率を上げるには、入力をチャンク単位で入力し、オペレーティングシステムに使用可能なバッファの多くを満たすように指示します。

チャンクで読み取りデータは、なぜBufReaderだけのデータを1バイトごとを読み取ることができない、ここではとても重要である理由あなたは不思議に思われるかもしれません。問題は、ストリームからデータを読み込むために、オペレーティングシステムのヘルプが必要であるということです。一方、私たちはオペレーティングシステムではなく、私たちはそのプロセスから何かがうまくいかない場合、それを混乱させないように、それから分離して作業します。したがって、オペレーティングシステムを呼び出すには、「カーネルモード」に移行する必要があります。これは「コンテキスト切り替え」を引き起こす可能性もあります。そのため、オペレーティングシステムを呼び出して1バイトごとに読み込むのは高価です。できるだけ少ない数のOSコールが必要なので、ストリームデータをバッチで取得します。

ブロックしないでストリームを待つ場合は、ノンブロッキングストリームが必要です。おそらくPipeReaderとMIO promises to have the required non-blocking stream support for pipes、が、私は今のところ、それをチェックアウトしていません。

ストリームの非ブロック性質は、それが可能に関係なく、オペレーティング・システムは、「読み込み短い」を好むかどうかのチャンクでデータを読み取るために行う必要があります。非ブロッキングストリームは決してブロックされないからです。ストリームにデータがない場合、単にそのことを指示します。あなたはブロッキングが別のスレッドで実行されるため、プライマリスレッドをブロックしません読み取るように産卵スレッドに頼る必要があるでしょう、非ブロッキングストリームの不在に

。また、オペレーティングシステムが "短い読み込み"を好まない場合は、行区切り文字にすぐに反応するために、バイト単位でストリームを読み込むこともできます。具体的な例を示します:https://gist.github.com/ArtemGr/db40ae04b431a95f2b78

+0

役立つ説明をしてくれてありがとう。私はMIOを調べ、それがうまくいかない場合は別のスレッドを使用します。 – jkiiski

関連する問題