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

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

» 2018年06月20日 05時00分 公開
[山本康彦BluewaterSoft/Microsoft MVP for Windows Development]
「.NET TIPS」のインデックス

連載「.NET TIPS」

 テキストファイルの読み書きは、特にそのファイルサイズが大きいと時間がかかるものだ。その待ち時間中にUIが無応答になってしまうのを避けるには、読み書き処理を非同期的に行えばよい。.NET Framework 4.5からは、そのようなコードが簡単に書けるようになっている。本稿では、非同期的にテキストファイルへ書き込む方法を解説する。

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

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


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

関連TIPS

 テキストファイルの内容を非同期的に読み込む方法は、次のTIPSを参照してほしい。

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


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

using static System.Console;

Imports System.Console

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

テキストファイルへ非同期的に書き込むメソッド

 テキストファイルを非同期的に読み込むときはStreamReaderクラス(System.IO名前空間)を使うが、非同期的に書き込むにはStreamWriterクラス(System.IO名前空間)を使う。

 StreamWriterクラスには、非同期的に書き込むためのメソッドとして次の2つが用意されている。

  • WriteAsyncメソッド:引数に与えられた文字列をそのままファイルに書き込む
  • WriteLineAsyncメソッド:引数に与えられた文字列をファイルに書き込み、さらに末尾に改行記号を書き込む

 WriteLineAsyncメソッドで追加される改行記号は、既定では「\r\n」(VBではvbCrLf)である。変更したい場合は、書き込む前にStreamWriterオブジェクトのNewLineプロパティに設定すればよい。

 WriteAsyncメソッド/WriteLineAsyncメソッドで書き込んだ後、StreamWriterオブジェクトをクローズする(=明示的にCloseメソッドを呼び出すか、あるいは、usingブロックから抜ける)と、完全にファイルへ書き出される。クローズするまでは、ファイルに書き出されていないテキストがバッファリングされてメモリ上に残っている可能性があるので、注意してほしい。例えばログの書き出しなどのように、ファイルを開いたままで(=StreamWriterオブジェクトをクローズすることなく)しばらく書き込みを中断するときには、StreamWriterオブジェクトのFlushAsyncメソッドを呼び出すようにする。そうすれば、そこまでの内容が完全にファイルへ書き出される。

 なお、複数のスレッドから同時に書き込む可能性がある場合には、スレッド間の排他ロックが必要になるが、非同期メソッドに対してはlockステートメントが使えない。代わりにSemaphoreSlimクラス(System.Threading名前空間)などを使う。詳しくは次のTIPSをご覧いただきたい。

非同期的に上書きするには?

 StreamWriterクラスのインスタンスを作り、WriteAsyncメソッド/WriteLineAsyncメソッドで書き込む(次のコード)。指定したファイルがないときは、新しく作られる。既にファイルがあるときは、その内容が上書きされる。文字エンコーディングを指定しなければUTF-8になる(文字エンコーディングを指定する方法は後述)。

 なお、上書きされるのは、「StreamWriterクラスのインスタンスを作るごと」にである。この例のように同じStreamWriterオブジェクトに対して、WriteAsyncメソッド/WriteLineAsyncメソッドを繰り返し呼び出しても上書きされず、どんどん追加されていく。

static async void OverWriteAsync()
{
  // 書き込むテキストファイル(実行ファイルと同じフォルダに作られる)
  const string TextFilePath = @".\sample_utf8.txt";

  // StreamWriterオブジェクトを作る
  using (var writer = new System.IO.StreamWriter(TextFilePath))
  {
    // 非同期的に文字列を書き込む
    await writer.WriteLineAsync("業務アプリInsider");
    await writer.WriteAsync("非同期的に書き込むには?");

  } // usingを抜けるときにファイルがクローズされる

  // 書き込んだ内容を読み取ってコンソールに出力する
  using (var reader = new System.IO.StreamReader(TextFilePath))
  {
    string allLines = await reader.ReadToEndAsync();
    WriteLine($"書き込んだファイルの内容:「{allLines}」");
  }
}

Async Sub OverWriteAsync()
  ' 書き込むテキストファイル(実行ファイルと同じフォルダに作られる)
  Const TextFilePath As String = ".\sample_utf8.txt"

  ' StreamWriterオブジェクトを作る
  Using writer = New System.IO.StreamWriter(TextFilePath)
    ' 非同期的に文字列を書き込む
    Await writer.WriteLineAsync("業務アプリInsider")
    Await writer.WriteAsync("非同期的に書き込むには?")
  End Using ' usingを抜けるときにファイルがクローズされる

  ' 書き込んだ内容を読み取ってコンソールに出力する
  Using reader = New System.IO.StreamReader(TextFilePath)
    Dim allLines As String = Await reader.ReadToEndAsync()
    WriteLine($"書き込んだファイルの内容:「{allLines}」")
  End Using
End Sub

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

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

OverWriteAsync();
WriteLine("OverWriteAsyncメソッドの呼び出し完了");
// 出力例:
// OverWriteAsyncメソッドの呼び出し完了
// 書き込んだファイルの内容:「業務アプリInsider
// 非同期的に書き込むには?」

OverWriteAsync()
WriteLine("OverWriteAsyncメソッドの呼び出し完了")
' 出力例:
' OverWriteAsyncメソッドの呼び出し完了
' 書き込んだファイルの内容:「業務アプリInsider
' 非同期的に書き込むには?」

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

非同期的に追加書き込みするには?

 StreamWriterクラスのインスタンスを作るとき、第2引数にtrueを指定する。後は、上書きするときと同様に、WriteAsyncメソッド/WriteLineAsyncメソッドで書き込む(次のコード)。指定したファイルがないときは、新しく作られる。既にファイルがあるときは、その後ろに書き込んだ内容が追加される。また、この例では、文字エンコーディングとしてシフトJISを指定している。

static async void AppendWriteAsync()
{
  // 書き込むテキストファイル(実行ファイルと同じフォルダに作られる)
  const string TextFilePath = @".\sample_sjis.txt";

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

  // StreamWriterオブジェクトを作る
  using (var writer = new System.IO.StreamWriter(TextFilePath, true, sjis))
  {
    // 非同期的に文字列を書き込む
    await writer.WriteAsync($"{System.DateTime.Now:mm:ss.fff} ");

  } // usingを抜けるときにファイルがクローズされる

  // 書き込んだ内容を読み取ってコンソールに出力する
  using (var reader = new System.IO.StreamReader(TextFilePath, sjis))
  {
    string allLines = await reader.ReadToEndAsync();
    WriteLine($"書き込んだファイルの内容:「{allLines}」");
  }
}

Async Sub AppendWriteAsync()
  ' 書き込むテキストファイル(実行ファイルと同じフォルダに作られる)
  Const TextFilePath As String = ".\sample_sjis.txt"

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

  ' StreamWriterオブジェクトを作る
  Using writer = New System.IO.StreamWriter(TextFilePath, True, sjis)
    ' 非同期的に文字列を書き込む
    Await writer.WriteAsync($"{System.DateTime.Now:mm:ss.fff} ")
  End Using ' usingを抜けるときにファイルがクローズされる

  ' 書き込んだ内容を読み取ってコンソールに出力する
  Using reader = New System.IO.StreamReader(TextFilePath, sjis)
    Dim allLines As String = Await reader.ReadToEndAsync()
    WriteLine($"書き込んだファイルの内容:「{allLines}」")
  End Using
End Sub

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

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

AppendWriteAsync();
WriteLine("AppendWriteAsyncメソッドの呼び出し完了");

// 出力例(1回目):
// AppendWriteAsyncメソッドの呼び出し完了
// 書き込んだファイルの内容:「59:22.424

// 出力例(2回目):
// AppendWriteAsyncメソッドの呼び出し完了
// 書き込んだファイルの内容:「59:22.424 59:23.439

AppendWriteAsync()
WriteLine("AppendWriteAsyncメソッドの呼び出し完了")

' 出力例(1回目):
' AppendWriteAsyncメソッドの呼び出し完了
' 書き込んだファイルの内容:「59:22.424

' 出力例(2回目):
' AppendWriteAsyncメソッドの呼び出し完了
' 書き込んだファイルの内容:「59:22.424 59:23.439

上記メソッドの使用例(上:C#、下:VB)
出力例を見ると、ファイルの書き込み処理が終わる前に「呼び出し完了」のメッセージが出力されている。ファイルの書き込み処理が非同期的に実行されているためだ。
ファイルの末尾に追加しているので、このコンソールアプリを呼び出すごとにファイルに書き込まれた内容は増えていく。

まとめ

 テキストファイルへ非同期的に書き込むには、StreamWriterクラスを使う。StreamWriterオブジェクトを作るときに、上書き/追加の区別(既定は上書き)、あるいは、文字エンコーディングを指定できる。

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

.NET TIPS

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

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

メールマガジン登録

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