.NET TIPS

外部プログラム実行時に処理が固まる場合には?[2.0、C#、VB]

デジタルアドバンテージ 遠藤 孝信
2008/11/06

 「TIPS:コンソール・アプリケーションの出力を取り込むには?」では、外部プログラムを実行し、その標準出力を読み込む方法について示しているが、これに若干手を加えれば、標準入力から文字列を読み取って、その処理結果を標準出力に出力するような外部プログラムにも対応できる。以下にそのサンプル・プログラムを示す。

// pipesync.cs

using System;
using System.Text;
using System.IO;
using System.Diagnostics;

class PipeSync {

  static void Main() {

    // 子プロセスで処理したいテキスト
    string text = File.ReadAllText("sample.txt",
            Encoding.GetEncoding("Shift_JIS"));

    Process p = new Process();

    // 子プロセスの実行ファイル名
    p.StartInfo.FileName = "cat.exe";

    // 子プロセスのオプション(もしあれば)
    p.StartInfo.Arguments = "-n";

    // コンソール・ウィンドウを開かない
    p.StartInfo.CreateNoWindow = true;

    // シェル機能を使用しない
    p.StartInfo.UseShellExecute = false;

    // 標準出力をリダイレクト
    p.StartInfo.RedirectStandardOutput = true;

    // 標準入力をリダイレクト
    p.StartInfo.RedirectStandardInput = true;

    p.Start(); // 子プロセスの実行開始

    using (StreamWriter sw = p.StandardInput) {
      sw.Write(text); // 子プロセスへの書き込み
    }

    // 子プロセスの出力の読み込み
    string output = p.StandardOutput.ReadToEnd();

    p.WaitForExit(); // 子プロセスが終了するのを待つ
    p.Dispose(); // 子プロセスの破棄

    Console.WriteLine(output); // 子プロセスの出力を表示
  }
}

// コンパイル方法:csc pipeasync.cs
外部プログラムの標準入出力をリダイレクトするC#のプログラム(pipesync.cs)
実行する場合には、子プロセスで処理したいテキスト、子プロセスの実行ファイル名、オプションの部分を修正する必要がある。

' pipesync.vb

Imports System
Imports System.Text
Imports System.IO
Imports System.Diagnostics

Class PipeSync
  Shared Sub main()

    ' 子プロセスで処理したいテキスト
    Dim text As String = File.ReadAllText("sample.txt", _
                Encoding.GetEncoding("Shift_JIS"))

    Dim p As New Process()

    ' 子プロセスの実行ファイル名
    p.StartInfo.FileName = "cat.exe"

    ' 子プロセスのオプション(もしあれば)
    p.StartInfo.Arguments = "-n"

    ' コンソール・ウィンドウを開かない
    p.StartInfo.CreateNoWindow = True

    ' シェル機能を使用しない
    p.StartInfo.UseShellExecute = False

    ' 標準出力をリダイレクト
    p.StartInfo.RedirectStandardOutput = True

    ' 標準入力をリダイレクト
    p.StartInfo.RedirectStandardInput = True

    p.Start() ' 子プロセスの実行開始

    Using sw As StreamWriter = p.StandardInput
      sw.Write(text) ' 子プロセスへの書き込み
    End Using

    ' 子プロセスの出力の読み取り
    Dim output As String = p.StandardOutput.ReadToEnd()

    p.WaitForExit() ' 子プロセスが終了するのを待つ
    p.Dispose() ' 子プロセスの破棄

    Console.WriteLine(output) ' 子プロセスの出力を表示
  End Sub
End Class

' コンパイル方法:vbc pipesync.vb
外部プログラムの標準入出力をリダイレクトするVBのプログラム(pipesync.vb)
実行する場合には、子プロセスで処理したいテキスト、子プロセスの実行ファイル名、オプションの部分を修正する必要がある。

 子プロセスへの標準入力も標準出力と同様にストリームであるため、その扱いは基本的に同じで、特に難しいポイントはない。

 しかしこのプログラムでは、子プロセスとして起動する外部プログラムや、子プロセスに処理させるデータの量によっては、(子プロセスの出力の読み取り部分の前で)プログラムが固まって(フリーズして)しまう場合がある。子プロセスとのデータのやり取りに使用される“パイプ”がいっぱいになり、詰まってしまうのである。

 これは、子プロセスの出力によりパイプがいっぱいになっているにもかかわらず、子プロセスの出力を処理せずに、パイプへの書き込みを続けようとするために発生する。このような場合には、子プロセスの標準入力への書き込みと、子プロセスの標準出力からの読み込みを非同期に行えばよい。その方法について次に述べる。

非同期に標準出力を読み込む

 Processクラスでは、子プロセスの標準出力からの読み込みを非同期に行う機能が用意されている。これを行うには、ProcessクラスのOutputDataReceivedイベントにイベント・ハンドラとなるメソッドを設定しておき、プロセスの開始後にBeginOutputReadLineメソッドを呼び出せばよい。

 OutputDataReceivedイベント・ハンドラは、子プロセスが標準出力に書き込むたび、プログラム本体とは非同期に呼び出される。書き込まれた文字列は、イベント・ハンドラの第2パラメータであるDataReceivedEventArgsオブジェクト(System.Diagnostics名前空間)のDataプロパティにより取得できる。

 以下に子プロセスの標準出力を非同期に読み込むサンプル・プログラムを示す。

// pipeasync.cs

using System;
using System.Text;
using System.IO;
using System.Diagnostics;

class PipeAsync {

  static StringBuilder output = new StringBuilder();

  static void Main() {

    // 子プロセスで処理したいテキスト
    string text = File.ReadAllText("sample.txt",
            Encoding.GetEncoding("Shift_JIS"));

    Process p = new Process();

    // 子プロセスの実行ファイル名
    p.StartInfo.FileName = "cat.exe";

    // 子プロセスのオプション(もしあれば)
    p.StartInfo.Arguments = "-n";

    // コンソール・ウィンドウを開かない
    p.StartInfo.CreateNoWindow = true;

    // シェル機能を使用しない
    p.StartInfo.UseShellExecute = false;

    // 標準出力をリダイレクト
    p.StartInfo.RedirectStandardOutput = true;

    // 標準入力をリダイレクト
    p.StartInfo.RedirectStandardInput = true;

    // イベント・ハンドラ設定
    p.OutputDataReceived += OutputHandler;

    p.Start(); // 子プロセスの実行開始
    p.BeginOutputReadLine(); // 子プロセスの出力読み込み開始

    using (StreamWriter sw = p.StandardInput) {
      sw.Write(text); // 子プロセスへの書き込み
    }

    p.WaitForExit(); // 子プロセスが終了するのを待つ
    p.Dispose(); // 子プロセスの破棄

    Console.WriteLine(output.ToString()); // 子プロセスの出力を表示
  }

  // 子プロセスが標準出力に出力したときに呼び出されるメソッド
  static void OutputHandler(object o, DataReceivedEventArgs args) {
    output.AppendLine(args.Data); // 出力されたデータを保存
  }
}

// コンパイル方法:csc pipeasync.cs
子プロセスの標準出力を非同期に読み込むC#のプログラム(pipeasync.cs)
実行する場合には、子プロセスで処理したいテキスト、子プロセスの実行ファイル名、オプションの部分を修正する必要がある。

' pipeasync.vb

Imports System
Imports System.Text
Imports System.IO
Imports System.Diagnostics

Class PipeAsync

  Shared output As New StringBuilder

  Shared Sub main()

    ' 子プロセスで処理したいテキスト
    Dim text As String = File.ReadAllText("sample.txt", _
                Encoding.GetEncoding("Shift_JIS"))

    Dim p As New Process()

    ' 子プロセスの実行ファイル名
    p.StartInfo.FileName = "cat.exe"

    ' 子プロセスのオプション(もしあれば)
    p.StartInfo.Arguments = "-n"

    ' コンソール・ウィンドウを開かない
    p.StartInfo.CreateNoWindow = True

    ' シェル機能を使用しない
    p.StartInfo.UseShellExecute = False

    ' 標準出力をリダイレクト
    p.StartInfo.RedirectStandardOutput = True

    ' 標準入力をリダイレクト
    p.StartInfo.RedirectStandardInput = True

    ' イベント・ハンドラ設定
    AddHandler p.OutputDataReceived, AddressOf OutputHandler

    p.Start() ' 子プロセスの実行開始

    p.BeginOutputReadLine() ' 子プロセスの出力読み込み開始

    Using sw As StreamWriter = p.StandardInput
      sw.Write(text) ' 子プロセスへの書き込み
    End Using

    p.WaitForExit() ' 子プロセスが終了するのを待つ
    p.Dispose() ' 子プロセスの破棄

    Console.WriteLine(output.ToString()) ' 子プロセスの出力を表示
  End Sub

  ' 子プロセスが標準出力に出力したときに呼び出されるメソッド
  Shared Sub OutputHandler(ByVal o As Object, ByVal args As DataReceivedEventArgs)
    output.AppendLine(args.Data) ' 出力されたデータを保存
  End Sub

End Class

' コンパイル方法:vbc pipeasync.vb
子プロセスの標準出力を非同期に読み込むVBのプログラム(pipeasync.vb)
実行する場合には、子プロセスで処理したいテキスト、子プロセスの実行ファイル名、オプションの部分を修正する必要がある。

 このプログラムでは、子プロセスの出力をStringBuilderクラス(System.Text名前空間)により蓄積している。これは単純な文字列の連結よりも処理が高速だ。またC#版では、イベント・ハンドラの記述で匿名メソッドを使用することにより、プログラムをもう少しシンプルにできる。End of Article

利用可能バージョン:.NET Framework 2.0以降
カテゴリ:クラス・ライブラリ 処理対象:Windows環境
使用ライブラリ:Processクラス(System.Diagnostics名前空間)
使用ライブラリ:DataReceivedEventArgsオブジェクト(System.Diagnostics名前空間)
使用ライブラリ:StringBuilderクラス(System.Text名前空間)
関連TIPS:コンソール・アプリケーションの出力を取り込むには?

この記事と関連性の高い別の.NET TIPS
プロセス情報を名前を基に取得するには?
多重起動禁止時に実行中のWindowsアプリケーションを最前面に表示するには?
コンソール・アプリケーションの出力を取り込むには?
デスクトップ上のすべてのメイン・ウィンドウを列挙するには?
標準入力(パイプ)からテキストを読み込むには?
このリストは、(株)デジタルアドバンテージが開発した
自動関連記事探索システム Jigsaw(ジグソー) により自動抽出したものです。
generated by

「.NET TIPS」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間