はじめに
今回は非同期using(await using)について紹介したいと思います。
概要
IAsyncDisposableを実装しているオブジェクトに対して、await usingを利用することで非同期な破棄処理を書くことができます。
private static async Task Sample() { // 1 -> 2 -> 3 await using (var exampleAsyncDisposable = new ExampleAsyncDisposable()) { Console.WriteLine("1番目に呼ばれる"); } Console.WriteLine("3番目に呼ばれる"); // 2 await using var exampleAsyncDisposable2 = new ExampleAsyncDisposable(); } public sealed class ExampleAsyncDisposable : IAsyncDisposable { public async ValueTask DisposeAsync() { // 破棄・解放処理を書く // ValueTaskを返すので、同期処理であったとしてもTaskのインスタンスがマネージドヒープに確保されない await Task.Delay(1000); Console.WriteLine("2番目に呼ばれる"); } }
また公式サンプルではConfigureAwait(false)を利用して、スレッドを元に戻らないようにしているようですね。(状況に応じて)
var exampleAsyncDisposable = new ExampleAsyncDisposable(); await using (exampleAsyncDisposable.ConfigureAwait(false)) { // Interact with the exampleAsyncDisposable instance. } Console.ReadLine();
必ずIAsyncDisposableを実装する必要はない
await usingはパターンベースらしく、必ずしもIAsyncDisposableを実装しなければならないわけではないようです。
非同期usingはパターン ベースになっています。 以下のように、IAsyncDisposableインターフェイスを実装せず、 単にDisposeAsyncメソッドを持っていればawait usingで使えます。
非同期ストリーム - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
返り値がawaitableなら良いっぽいです。以下Awaitableパターンの説明。
ufcpp.net
またclassではなくstructでもOKです。
IDisposableとIAsyncDisposable
通常、IAsyncDisposable インターフェイスを実装するとき、そのクラスでは IDisposable インターフェイスも実装します。 IAsyncDisposable インターフェイスの推奨される実装パターンは、同期か非同期のいずれかの破棄のために準備をすることです。 クラスの同期の破棄が不可能な場合は、IAsyncDisposable を持つことだけが許容されます。 破棄パターンの実装に関するガイダンスのすべての説明は、非同期の実装にも適用されます。
DisposeAsync メソッドの実装 | Microsoft Learn
IAsyncDisposableは非同期で破棄処理を書く必要がある可能性があるときに実装し、IAsyncDisposableを実装するならIDisposableも実装せよということらしいですね。
また混在している場合、どっちが呼ばれるかは以下の通り。
ちなみに、Dispose(IDisposableインターフェイス)とDisposeAsync(IAsyncDisposableインターフェイス)の両方を実装をしている場合、それぞれ同期版using、非同期usingでしか呼ばれません。 非同期版が同期版を兼ねたりはしませんし、その逆もまたしかり。