はじめに
今回は非同期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でしか呼ばれません。 非同期版が同期版を兼ねたりはしませんし、その逆もまたしかり。