連載
» 2018年06月13日 05時00分 公開

.NET TIPS:テキストファイルの内容を非同期的に読み込むには?[C#/VB、.NET 4.5]

.NET Framework 4.5以降で追加された「テキストファイルの内容を非同期で読み込むためのメソッド」の利用法を説明する。

[山本康彦,BluewaterSoft/Microsoft MVP for Windows Development]
「.NET TIPS」のインデックス

連載「.NET TIPS」

 テキストファイルを読み込むとき、そのファイルサイズが大きくて読み込みに時間がかかるようだと、その完了を待っている間はUIが無応答になってしまう。それを回避するにはファイル読み込みを非同期処理にすればよいのだが、これまではちょっと面倒だった。本稿では、.NET Framework 4.5で導入された簡単な方法を紹介する。

POINT テキストファイルの内容を非同期的に読み込む方法

テキストファイルの内容を非同期的に読み込む方法まとめ テキストファイルの内容を非同期的に読み込む方法まとめ


 特定のトピックをすぐに知りたいという方は以下のリンクを活用してほしい。

関連TIPS

 テキストファイルの内容を読み込むには、その目的や利用している.NET Frameworkのバージョンによって、さまざまな方法がある。適切な方法を選んでほしい。


 なお、本稿に掲載したサンプルコードをそのまま試すにはVisual Studio 2015以降が必要である。サンプルコードはコンソールアプリの一部であり、コードの冒頭に以下の宣言が必要となる。また、サンプルということで、例外処理は省略している。実際には、ファイルが存在していなかったり、アクセス権がなくて開けなかったりしたときなどに例外が発生するので、適切にtry〜catchしていただきたい。

using static System.Console;

Imports System.Console

本稿のサンプルコードに必要な宣言(上:C#、下:VB)

ReadLineAsync:1行ずつ読み込む

 テキストファイルを非同期的に読み込むには、StreamReaderクラス(System.IO名前空間)が使える。1行ずつ非同期的に読み込むなら、そのReadLineAsyncメソッドを使う(次のコード)。

 StreamReaderオブジェクトを得る方法には幾つかあるが、ここではFileクラス(System.IO名前空間)のOpenTextメソッドを使った。また、文字エンコーディングを指定していないので、読み込むテキストファイルはUTF-8で書かれていなければならない。

static async void ReadLineAsync()
{
  // 読み取るテキストファイル(実行ファイルと同じフォルダに置く)
  const string TextFile = @".\sample_utf8.txt";

  // テキストファイルのStreamReaderオブジェクトを得る
  using (System.IO.StreamReader reader = System.IO.File.OpenText(TextFile))
  {
    string line;
    int count = 1;
    // ReadLineAsyncメソッドで非同期的に1行ずつ読み込み、変数lineに格納する
    while ((line = await reader.ReadLineAsync()) != null)
      WriteLine($"{count++}行目:{line}"); // 読み込んだ内容をコンソールに出力する
  }
}

Async Sub ReadLineAsync()
  ' 読み取るテキストファイル(実行ファイルと同じフォルダに置く)
  Const TextFile As String = ".\sample_utf8.txt"

  ' テキストファイルのStreamReaderオブジェクトを得る
  Using reader As System.IO.StreamReader = System.IO.File.OpenText(TextFile)
    Dim count As Integer = 1
    While (True)
      ' ReadLineAsyncメソッドで非同期的に1行ずつ読み込み、変数lineに格納する
      Dim line As String = Await reader.ReadLineAsync()
      If (line Is Nothing) Then
        Exit While
      End If
      WriteLine($"{count}行目:{line}") ' 読み込んだ内容をコンソールに出力する
      count += 1
    End While
  End Using
End Sub

ReadLineAsyncメソッドを使って非同期的に1行ずつ読み込むメソッド(上:C#、下:VB)
非同期メソッドを呼び出してその完了を待機するには、呼び出す部分にawaitキーワードが、また、メソッドのシグネチャにasyncキーワードが必要だ。また、StreamReaderクラスはIDisposableインタフェースを実装しているので、このコードのようにusing句を使って、間違いなくリソースが解放されるようにする。
なお、このメソッドを呼び出す側で、このメソッドの完了を待機したい場合は、このメソッドの返値をTask型(System.Threading.Tasks名前空間)に変える(メソッド本体のコードは変わらず)。

 上のメソッドをコンソールアプリのMainメソッド内から呼び出してみると、次のコードのようになる。

ReadLineAsync();
WriteLine("ReadLineAsyncメソッドの呼び出し完了");
// 出力例:
// ReadLineAsyncメソッドの呼び出し完了
// 1行目:業務アプリInsider
// 2行目:テキストファイルの内容を非同期的に読み込むには?

ReadLineAsync()
WriteLine("ReadLineAsyncメソッドの呼び出し完了")
' 出力例:
' ReadLineAsyncメソッドの呼び出し完了
' 1行目:業務アプリInsider
' 2行目:テキストファイルの内容を非同期的に読み込むには?

上記メソッドの使用例(上:C#、下:VB)
出力例を見ると、ファイルの読み込み処理が終わる前に「呼び出し完了」のメッセージが出力されている。ファイルの読み込み処理が非同期的に実行されているためだ。ファイルの読み込みは非同期に実行されるが、場合によっては、読み込みが先に完了して上の出力例とは異なる順番で表示されることもある(以下同様)。

ReadToEndAsync:まとめて読み込む

 テキストファイルを非同期的に読み込むには、StreamReaderクラス(System.IO名前空間)が使える。ファイルの全体をまとめて一気に読み込むなら、そのReadToEndAsyncメソッドを使う(次のコード)。

 StreamReaderオブジェクトを得る方法には幾つかあるが、ここではStreamReaderクラスのコンストラクタを使った。また、文字エンコーディングを指定していないので、読み込むテキストファイルはUTF-8で書かれていなければならない。

static async void ReadToEndAsync()
{
  // 読み取るテキストファイル(実行ファイルと同じフォルダに置く)
  const string TextFile = @".\sample_utf8.txt";

  // テキストファイルのStreamReaderオブジェクトを得る
  using (var reader = new System.IO.StreamReader(TextFile))
  {
    // ReadToEndAsyncメソッドで非同期的にファイル全体を読み込み、変数に格納する
    string allLines = await reader.ReadToEndAsync();
    WriteLine($"UTF8ファイルの内容:「{allLines}」"); // 読み込んだ内容を出力する
  }
}

Async Sub ReadToEndAsync()
  ' 読み取るテキストファイル(実行ファイルと同じフォルダに置く)
  Const TextFile As String = ".\sample_utf8.txt"

  ' テキストファイルのStreamReaderオブジェクトを得る
  Using reader = New System.IO.StreamReader(TextFile)
    ' ReadToEndAsyncメソッドで非同期的にファイル全体を読み込み、変数に格納する
    Dim allLines As String = Await reader.ReadToEndAsync()
    WriteLine($"UTF8ファイルの内容:「{allLines}」"' 読み込んだ内容を出力する
  End Using
End Sub

ReadToEndAsyncメソッドを使ってまとめて非同期的に読み込むメソッド(上:C#、下:VB)
非同期メソッドを呼び出してその完了を待機するには、呼び出す部分にawaitキーワードが、また、メソッドのシグネチャにasyncキーワードが必要だ。また、StreamReaderクラスはIDisposableインタフェースを実装しているので、このコードのようにusing句を使って、間違いなくリソースが解放されるようにする。
なお、このメソッドを呼び出す側で、このメソッドの完了を待機したい場合は、このメソッドの返値をTask型(System.Threading.Tasks名前空間)に変える(メソッド本体のコードは変わらず)。

 上のメソッドをコンソールアプリのMainメソッド内から呼び出してみると、次のコードのようになる。

ReadToEndAsync();
WriteLine("ReadToEndAsyncメソッドの呼び出し完了");
// 出力例:
// ReadToEndAsyncメソッドの呼び出し完了
// UTF8ファイルの内容:「業務アプリInsider
// テキストファイルの内容を非同期的に読み込むには?」

ReadToEndAsync()
WriteLine("ReadToEndAsyncメソッドの呼び出し完了")
' 出力例:
' ReadToEndAsyncメソッドの呼び出し完了
' UTF8ファイルの内容:「業務アプリInsider
' テキストファイルの内容を非同期的に読み込むには?」

上記メソッドの使用例(上:C#、下:VB)
出力例を見ると、ファイルの読み込み処理が終わる前に「呼び出し完了」のメッセージが出力されている。ファイルの読み込み処理が非同期的に実行されているためだ。

文字エンコーディングを指定するには?

 読み込むテキストファイルの文字エンコーディングがUTF-8以外の場合は、StreamReaderクラス(System.IO名前空間)のインスタンスを作るときにエンコーディングを指定する。その後の読み込む処理は、ここまでに述べてきた方法と同じである。例えば、シフトJISのテキストファイルの全体を非同期的に一気に読み込むメソッドは、次のコードのようになる。

 GetEncodingメソッドに指定できる文字列については、Encodingクラス(System.Text名前空間)のドキュメントを参照してほしい。

static async void ReadSjisAsync()
{
  // 読み取るテキストファイル(実行ファイルと同じフォルダに置く)
  const string TextFile = @".\sample_sjis.txt";

  // シフトJISの文字エンコーディングオブジェクトを用意する
  var sjis = System.Text.Encoding.GetEncoding("shift_jis");

  // StreamReaderオブジェクトを作るときにエンコーディングを指定する
  using (var reader = new System.IO.StreamReader(TextFile, sjis))
  {
    string allLines = await reader.ReadToEndAsync();
    WriteLine($"シフトJISファイルの内容:「{allLines}」");
  }
}

Async Sub ReadSjisAsync()
  ' 読み取るテキストファイル(実行ファイルと同じフォルダに置く)
  Const TextFile As String = ".\sample_sjis.txt"

  ' シフトJISの文字エンコーディングオブジェクトを用意する
  Dim sjis = System.Text.Encoding.GetEncoding("shift_jis")

  ' StreamReaderオブジェクトを作るときにエンコーディングを指定する
  Using reader = New System.IO.StreamReader(TextFile, sjis)
    Dim allLines As String = Await reader.ReadToEndAsync()
    WriteLine($"シフトJISファイルの内容:「{allLines}」")
  End Using
End Sub

シフトJISのテキストファイルをまとめて非同期的に読み込むメソッド(上:C#、下:VB)
非同期メソッドを呼び出してその完了を待機するには、呼び出す部分にawaitキーワードが、また、メソッドのシグネチャにasyncキーワードが必要だ。また、StreamReaderクラスはIDisposableインタフェースを実装しているので、このコードのようにusing句を使って、間違いなくリソースが解放されるようにする。
なお、このメソッドを呼び出す側で、このメソッドの完了を待機したい場合は、このメソッドの返値をTask型(System.Threading.Tasks名前空間)に変える(メソッド本体のコードは変わらず)。

 上のメソッドをコンソールアプリのMainメソッド内から呼び出してみると、次のコードのようになる。

ReadSjisAsync();
WriteLine("ReadSjisAsyncメソッドの呼び出し完了");
// 出力例:
// ReadSjisAsyncメソッドの呼び出し完了
// シフトJISファイルの内容:「業務アプリInsider
// テキストファイルの内容を非同期的に読み込むには?」

ReadSjisAsync()
WriteLine("ReadSjisAsyncメソッドの呼び出し完了")
' 出力例:
' ReadSjisAsyncメソッドの呼び出し完了
' シフトJISファイルの内容:「業務アプリInsider
' テキストファイルの内容を非同期的に読み込むには?」

上記メソッドの使用例(上:C#、下:VB)
出力例を見ると、ファイルの読み込み処理が終わる前に「呼び出し完了」のメッセージが出力されている。ファイルの読み込み処理が非同期的に実行されているためだ。

まとめ

 テキストファイルを非同期的に読み込むには、StreamReaderクラスを使う。1行ずつ非同期的に読み込むならReadLineAsyncメソッドを、ファイルの全体をまとめて一気に読み込むならReadToEndAsyncメソッドを利用する。また、文字エンコーディングは、StreamReaderクラスのコンストラクタで指定する。

 なお、すでに開かれているファイルを読み込むときには、FileStreamクラスを併用する。詳細は「オープン中のファイルにアクセスするには?[C#、VB]」をご覧いただきたい。

「.NET TIPS」のインデックス

.NET TIPS

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。