.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」

@IT Special

- PR -

TechTargetジャパン

Insider.NET フォーラム 新着記事
  • Visual Studio 2017が目指す世界とは? (2017/3/24)
     Visual Studio 2017はもはやWindowsアプリ開発者のためだけのものではない。どんなアプリを開発できるのかを見てみよう
  • foreachループで現在の繰り返し回数を使うには? (2017/3/23)
     LINQのSelect拡張メソッドを使用して、foreachループの中で現在が何回目の繰り返しなのか、そのインデックスを得る方法を紹介する(C# 7/VB 15対応)
  • Listの要素を並べ替えるには? (2017/3/22)
     LINQのOrderBy/ThenByなどの拡張メソッドとListクラスのSortメソッドを利用して、Listの要素を並べ替える方法を解説する
  • .NET Portability Analyzer (2017/3/21)
     .NET Portability Analyzerは、さまざまなプラットフォームをまたいだ.NETプログラムの移植性をチェックするためのツール
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)
- PR -

イベントカレンダー

PickUpイベント

- PR -

アクセスランキング

もっと見る

ホワイトペーパーTechTargetジャパン

注目のテーマ

Insider.NET 記事ランキング

本日 月間
ソリューションFLASH