.NET Framework 4.5以降でFileStreamクラスに追加されたReadAsync/WriteAsyncメソッドを使い、非同期的にバイナリファイルを読み書きする方法を説明する。
ファイルの読み書きは、特にそのファイルサイズが大きいと時間がかかるものだ。その待ち時間中にUIが無応答になってしまうのを避けるには、読み書き処理を非同期的に行えばよい。.NET Framework 4.5からは、そのようなコードが簡単に書けるようになっている。本稿では、非同期的にバイナリファイルを読み書きする方法を解説する。
特定のトピックをすぐに知りたいという方は以下のリンクを活用してほしい。
テキストファイルの内容を非同期的に読み書きする方法は、次のTIPSを参照してほしい。
バイナリファイルを読み書きするには、その目的や利用している.NET Frameworkのバージョンによって、さまざまな方法がある。適切な方法を選んでほしい。
なお、本稿に掲載したサンプルコードをそのまま試すにはVisual Studio 2015以降が必要である。サンプルコードはコンソールアプリの一部であり、コードの冒頭に以下の宣言が必要となる。また、サンプルということで、例外処理は省略している。実際には、指定したパスが存在していなかったり、アクセス権がなくて書き込めなかったりしたときなどに例外が発生するので、適切にtry〜catchしていただきたい。
using static System.Console;
Imports System.Console
バイナリファイルを非同期的に読み書きするには、FileStreamクラス(System.IO名前空間)を利用する。
同期的に読み書きするメソッドとしてReadメソッドとWriteメソッドがあり、その非同期バージョンとしてReadAsyncメソッドとWriteAsyncメソッドが.NET Framework 4.5で追加された。
その引数や使い方は同期バージョンであるReadメソッド/Writeメソッドと同様なので、そちらについて解説した「バイナリ・ファイルを読み書きするには?[C#、VB]」をご覧いただきたい。
同期バージョンとの大きな違いは、非同期バージョンでは積極的な理由がない限り一気に読み書きしてしまえばよいところだ。同期バージョンでは、UIがフリーズしないように、ある程度の小さいサイズに分けて読み書きする必要があった。非同期バージョンではその心配がないので、大きなファイルでもいっぺんに読み書きしてしまえばよい。小分けにしたい積極的な理由としては、メモリに載り切らない大きなファイルを扱うときや、読み書きの進捗表示を出したいときなどが考えられる。
なお、(ファイルを上書きするのではなく)ファイルの末尾に追加したいときは、ファイルを開いた後でSeekメソッドを使って書き込み位置を末尾に移動するか、あるいは、ファイルを開くときにFileStreamコンストラクタ引数でFileMode.Appendを指定する。指定するFileMode列挙体(System.IO名前空間)は次の通りだ。
そして、WriteAsyncメソッドで書き込んだ後、FileStreamオブジェクトをクローズする(=明示的にCloseメソッドを呼び出すか、あるいは、usingブロックから抜ける)と、完全にファイルへ書き出される。クローズするまでは、ファイルに書き出されていないデータがバッファリングされてメモリ上に残っている可能性があるので、注意してほしい。例えばログの書き出しなどのように、ファイルを開いたままで(=FileStreamオブジェクトをクローズすることなく)しばらく書き込みを中断するときには、FileStreamオブジェクトのFlushAsyncメソッドを呼び出すようにする。そうすれば、そこまでの内容が完全にファイルへ書き出される。
また、複数のスレッドから同時に書き込む可能性がある場合には、スレッド間の排他ロックが必要になるが、非同期メソッドに対してはlockステートメントが使えない。代わりにSemaphoreSlimクラス(System.Threading名前空間)などを使う。詳しくは次のTIPSをご覧いただきたい。
実際にバイナリファイルへ書き出して、それを読み込むサンプルを次のコードに示す。この例では、書き込み用に開くときにFileMode.Createを指定しているので上書きになる(追加にしたいときはFileMode.Appendにする)。
static async void BinaryReadWriteAsync(byte[] data)
{
// 読み書きするファイル(実行ファイルと同じフォルダに作られる)
const string FilePath = @".\sample.dat";
// バイナリファイル書き込み
// ファイルを上書きモードで開く(ファイルがないときは作る)
// 追加モードにするにはFileModeをAppendに変える
using (var fs = new System.IO.FileStream(FilePath,
System.IO.FileMode.Create, System.IO.FileAccess.Write))
{
// バイナリデータを非同期的に書き込む
await fs.WriteAsync(data, 0, data.Length);
} // usingを抜けるとき、ファイルへ完全に書き込まれる
// バイナリファイル読み込み
byte[] result; // データを格納する配列
// ファイルを読み取りモードで開く
using (var fs = new System.IO.FileStream(FilePath,
System.IO.FileMode.Open, System.IO.FileAccess.Read))
{
// データ格納用の配列を確保する
result = new byte[fs.Length];
// バイナリデータを非同期的に読み込む
await fs.ReadAsync(result, 0, (int)fs.Length);
}
// 読み込んだ内容をコンソールへ出力する
for(int i=0; i<result.Length; i++)
{
Write($"{result[i]:X2} ");
if (i % 16 == 7)
Write(" ");
if (i % 16 == 15)
WriteLine();
}
WriteLine();
}
Async Sub BinaryReadWriteAsync(data As Byte())
' 読み書きするファイル(実行ファイルと同じフォルダに作られる)
Const FilePath As String = ".\sample.dat"
' バイナリファイル書き込み
' ファイルを上書きモードで開く(ファイルがないときは作る)
' 追加モードにするにはFileModeをAppendに変える
Using fs = New System.IO.FileStream(FilePath,
System.IO.FileMode.Create, System.IO.FileAccess.Write)
' バイナリデータを非同期的に書き込む
Await fs.WriteAsync(data, 0, data.Length)
End Using 'usingを抜けるとき、ファイルへ完全に書き込まれる
' バイナリファイル読み込み
Dim result() As Byte ' データを格納する配列
' ファイルを読み取りモードで開く
Using fs = New System.IO.FileStream(FilePath,
System.IO.FileMode.Open, System.IO.FileAccess.Read)
' データ格納用の配列を確保する
ReDim result(fs.Length - 1)
' バイナリデータを非同期的に読み込む
Await fs.ReadAsync(result, 0, fs.Length)
End Using
' 読み込んだ内容をコンソールへ出力する
For i As Integer = 0 To (result.Length - 1)
Write($"{result(i):X2} ")
If (i Mod 16 = 7) Then
Write(" ")
End If
If (i Mod 16 = 15) Then
WriteLine()
End If
Next
End Sub
上のメソッドをコンソールアプリのMainメソッド内から呼び出してみると、次のコードのようになる。
// 書き込むデータ
string src = ".NET TIPS:バイナリ・ファイルを読み書きするには?";
byte[] bytes = new byte[src.Length * sizeof(char)];
System.Buffer.BlockCopy(src.ToCharArray(), 0, bytes, 0, bytes.Length);
// 非同期読み書きメソッドを呼び出し
BinaryReadWriteAsync(bytes);
WriteLine("BinaryReadWriteAsyncメソッドの呼び出し完了");
// 出力例:
// BinaryReadWriteAsyncメソッドの呼び出し完了
// 2E 00 4E 00 45 00 54 00 20 00 54 00 49 00 50 00
// 53 00 1A FF D0 30 A4 30 CA 30 EA 30 FB 30 D5 30
// A1 30 A4 30 EB 30 92 30 AD 8A 7F 30 F8 66 4D 30
// 59 30 8B 30 6B 30 6F 30 1F FF
' 書き込むデータ
Dim src As String = ".NET TIPS:バイナリ・ファイルを読み書きするには?"
Dim bytes(src.Length * Len(New Char()) - 1) As Byte
System.Buffer.BlockCopy(src.ToCharArray(), 0, bytes, 0, bytes.Length)
' 非同期読み書きメソッドを呼び出し
BinaryReadWriteAsync(bytes)
WriteLine("BinaryReadWriteAsyncメソッドの呼び出し完了")
' 出力例:
' BinaryReadWriteAsyncメソッドの呼び出し完了
' 2E 00 4E 00 45 00 54 00 20 00 54 00 49 00 50 00
' 53 00 1A FF D0 30 A4 30 CA 30 EA 30 FB 30 D5 30
' A1 30 A4 30 EB 30 92 30 AD 8A 7F 30 F8 66 4D 30
' 59 30 8B 30 6B 30 6F 30 1F FF
バイナリファイルを非同期的に読み書きするには、FileStreamクラスを使う。書き込み用としてFileStreamオブジェクトを作るときに、上書き/追加の区別を指定できる。分割して読み書きする積極的な理由がなければ、ファイル全体をまとめて読み書きすればよい。非同期的な処理はUIをブロックしないからだ。
利用可能バージョン:.NET Framework 4.5以降
カテゴリ:クラスライブラリ 処理対象:バイナリファイル
使用ライブラリ:FileStreamクラス(System.IO名前空間)
関連TIPS:バイナリ・ファイルを読み書きするには?
関連TIPS:バイナリ・ファイルを簡単に読み書きするには?[2.0のみ、C#、VB]
関連TIPS:テキストファイルの内容を非同期的に読み込むには?[C#/VB、.NET 4.5]
関連TIPS:テキストファイルの内容を非同期的に書き込むには?[C#/VB、.NET 4.5]
関連TIPS:ファイルにテキストを書き込むには?[C#、VB]
関連TIPS:テキスト・ファイルの内容を簡単に書き込むには?[2.0のみ、C#、VB]
関連TIPS:オープン中のファイルにアクセスするには?[C#、VB]
関連TIPS:async/awaitで例外処理をするには?[C#/VB]
関連TIPS:非同期:awaitを含むコードをロックするには?(SemaphoreSlim編)[C#、VB]
関連TIPS:非同期:awaitを含むコードをロックするには?(AsyncLock編)[C#、VB]
関連TIPS:ファイルをコピー/削除/リネーム/移動するには?
関連TIPS:VB.NETで配列を宣言するには?
関連TIPS:構文:クラス名を書かずに静的メソッドを呼び出すには?[C# 6.0]
関連TIPS:VB.NETでクラス名を省略してメソッドや定数を利用するには?
関連TIPS:数値を右詰めや0埋めで文字列化するには?[C#、VB]
Copyright© Digital Advantage Corp. All Rights Reserved.