C#のawait usingとは?IAsyncDisposableの使い方とusingとの違いをわかりやすく解説

はじめに

C#で非同期処理を書いていると、asyncawaitだけでなく、await usingという構文を見かけることがあります。

C#
await using var resource = new AsyncResource();

一見すると通常のusingawaitが付いただけのように見えますが、await usingは単に「非同期処理の中でusingを使う」ための構文ではありません。

await usingは、リソースの破棄処理を非同期で待機するための構文です。

通常のusingでは、スコープを抜けるとDispose()が呼び出されます。一方、await usingでは、スコープを抜けるとDisposeAsync()が呼び出され、その完了をawaitします。

つまり、結論からいうと次のような違いがあります。

C#
using        // Dispose() を呼び出す
await using // DisposeAsync() を呼び出して await する

この記事では、C#のawait usingとは何か、IAsyncDisposableの使い方、通常のusingとの違い、実務での使いどころをわかりやすく解説します。

1. C#のawait usingとは?

1-1. await usingは非同期でリソースを破棄するための構文

await usingは、C#において非同期にリソースを解放するための構文です。

通常、ファイル、ストリーム、データベース接続、ネットワーク接続などのリソースは、使い終わったあとに明示的な解放処理が必要です。

C#では、このようなリソース解放にusingを使います。

C#
using var stream = new FileStream("sample.txt", FileMode.Open);

この場合、スコープを抜けるとDispose()が呼ばれます。

一方、リソース解放自体に非同期処理が必要な場合は、Dispose()ではなくDisposeAsync()を使います。そのDisposeAsync()を自動的に呼び出して待機してくれるのがawait usingです。

C#
await using var resource = new AsyncResource();

このコードでは、スコープを抜けるタイミングで次のような処理が行われます。

C#
await resource.DisposeAsync();

つまり、await usingは「非同期処理の中で使えるusing」ではなく、より正確には非同期Disposeを行うためのusingです。

1-2. C# 8.0で導入された非同期Disposeの仕組み

await usingは、C# 8.0で導入されました。

C# 8.0では、非同期ストリームやnullable参照型などとあわせて、非同期リソース解放の仕組みとしてIAsyncDisposableインターフェイスが追加されました。

従来のC#では、リソース解放の標準的な仕組みとしてIDisposableが使われていました。

C#
public interface IDisposable
{
void Dispose();
}

しかし、Dispose()は戻り値がvoidであり、非同期処理をawaitできません。

そのため、リソース解放時に非同期処理が必要な場合でも、従来は同期的に待機したり、無理にブロッキングしたりする必要がありました。

そこで追加されたのがIAsyncDisposableです。

C#
public interface IAsyncDisposable
{
ValueTask DisposeAsync();
}

IAsyncDisposableを実装した型は、await usingで安全に非同期破棄できます。

1-3. await usingで呼び出されるDisposeAsyncとは

await usingで呼び出されるのは、Dispose()ではなくDisposeAsync()です。

たとえば、次のようなクラスがあるとします。

C#
public class AsyncResource : IAsyncDisposable
{
public async ValueTask DisposeAsync()
{
await Task.Delay(100);
Console.WriteLine("非同期でリソースを破棄しました");
}
}

このクラスをawait usingで使うと、スコープを抜けるときにDisposeAsync()が呼び出されます。

C#
await using var resource = new AsyncResource();

Console.WriteLine("リソースを使用中");

実行イメージは次のとおりです。

リソースを使用中
非同期でリソースを破棄しました

重要なのは、DisposeAsync()の完了を待ってから次の処理へ進む点です。

これにより、リソース解放が完了する前に後続処理が進んでしまう問題を防げます。

1-4. まず押さえるべき結論:usingはDispose、await usingはDisposeAsyncを呼ぶ

usingawait usingの違いは、まず次のように理解するとわかりやすいです。

C#
using var resource = new Resource();

これは、スコープ終了時に次の処理を行います。

C#
resource.Dispose();

一方で、次のコードがあります。

C#
await using var resource = new AsyncResource();

これは、スコープ終了時に次の処理を行います。

C#
await resource.DisposeAsync();

つまり、基本的な違いは次のとおりです。

構文呼び出されるメソッド対応インターフェイス処理
usingDispose()IDisposable同期的に破棄する
await usingDisposeAsync()IAsyncDisposable非同期で破棄する

await usingを理解するうえでは、この違いを最初に押さえておくことが重要です。

2. await usingが必要になる背景

2-1. 従来のusingとIDisposableの役割

従来のC#では、リソース解放にはIDisposableusingが使われてきました。

たとえば、ファイルを読み込む処理では次のように書きます。

C#
using var reader = new StreamReader("sample.txt");

var text = reader.ReadToEnd();

このコードでは、readerが不要になったタイミングで自動的にDispose()が呼ばれます。

usingを使わずに書くと、次のようなtry-finallyに近い処理になります。

C#
var reader = new StreamReader("sample.txt");

try
{
var text = reader.ReadToEnd();
}
finally
{
reader.Dispose();
}

usingの役割は、例外が発生しても確実にDispose()を呼び出すことです。

これにより、ファイルハンドル、メモリ、ネットワーク接続、データベース接続などのリソースを安全に解放できます。

2-2. 非同期処理ではDisposeだけでは不十分になる理由

非同期処理が一般的になると、リソースの解放処理にも非同期処理が必要になるケースが増えました。

たとえば、次のような処理です。

  • バッファに残ったデータを非同期でフラッシュする

  • ネットワーク越しに切断処理を通知する

  • データベース接続を非同期で閉じる

  • ストリームを非同期で閉じる

  • 外部サービスとの通信終了処理を待つ

これらの処理は、I/O待ちを伴う可能性があります。

しかし、通常のDispose()は同期メソッドです。

C#
void Dispose()

戻り値がvoidなので、内部で非同期処理を自然にawaitできません。

無理に非同期処理を同期的に待つと、次のような問題が起きることがあります。

  • スレッドをブロックしてしまう

  • パフォーマンスが低下する

  • デッドロックの原因になる

  • 非同期処理の利点が失われる

  • 破棄処理の完了タイミングが不明確になる

そのため、非同期処理に対応した破棄の仕組みが必要になりました。

2-3. ファイル・ネットワーク・DB接続などで非同期破棄が必要なケース

await usingが役立つ代表的なケースは、I/Oを伴うリソースです。

たとえば、ファイル操作では、書き込みバッファに残っているデータをストレージへ反映する処理が必要になることがあります。

C#
await using var stream = new FileStream(
"sample.txt",
FileMode.Create,
FileAccess.Write,
FileShare.None,
bufferSize: 4096,
useAsync: true);

このような非同期対応のストリームでは、破棄時に内部の非同期処理を待機できる方が自然です。

また、データベース接続でも同様です。

C#
await using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();

データベース接続の終了処理では、接続プールへの返却や通信の終了処理が発生する場合があります。こうした処理を非同期で行える型では、await usingを使うことで安全に後片付けできます。

ネットワーク通信やクラウドサービスのクライアントでも、切断処理や送信完了待ちが必要になる場合があります。

2-4. IAsyncDisposableが解決する課題

IAsyncDisposableは、非同期リソース解放のための標準インターフェイスです。

C#
public interface IAsyncDisposable
{
ValueTask DisposeAsync();
}

このインターフェイスにより、C#では次のような書き方ができます。

C#
await using var resource = new AsyncResource();

IAsyncDisposableが解決する主な課題は次のとおりです。

課題IAsyncDisposableによる解決
Dispose()ではawaitできないDisposeAsync()で非同期に待てる
破棄時のI/O処理を同期的に待つ必要がある非同期のまま完了を待てる
リソース解放の完了タイミングが曖昧になるawait usingで確実に待機できる
独自の非同期クリーンアップ手段がばらつく標準インターフェイスとして統一できる

つまり、IAsyncDisposableawait usingは、非同期時代のリソース解放を安全に扱うための仕組みです。

3. await usingの基本的な書き方

3-1. await usingステートメントの構文

await usingには、ブロックを明示する書き方があります。

C#
await using (var resource = new AsyncResource())
{
await resource.DoSomethingAsync();
}

この場合、{ }のブロックを抜けるときにDisposeAsync()が呼ばれます。

C#
await resource.DisposeAsync();

例外が発生した場合でも、ブロックを抜けるときにDisposeAsync()が実行されます。

C#
await using (var resource = new AsyncResource())
{
throw new InvalidOperationException();
}

この場合でも、resource.DisposeAsync()は呼び出されます。

通常のusingと同じように、await usingもリソースを確実に解放するための構文です。ただし、呼び出すメソッドがDisposeAsync()である点が異なります。

3-2. await using宣言の構文

C#では、using宣言と同じようにawait using宣言も使えます。

C#
await using var resource = new AsyncResource();

await resource.DoSomethingAsync();

この書き方では、明示的なブロックはありません。

resourceは、現在のスコープを抜けるタイミングで破棄されます。

たとえば、メソッド内で次のように書いた場合です。

C#
public async Task ExecuteAsync()
{
await using var resource = new AsyncResource();

await resource.DoSomethingAsync();

Console.WriteLine("処理完了");
}

この場合、ExecuteAsyncメソッドを抜ける直前にDisposeAsync()が呼び出されます。

await using宣言はコードが簡潔になるため、実務でもよく使われます。

3-3. ブロックを抜けたタイミングでDisposeAsyncが実行される流れ

await usingでは、リソースの使用範囲が終わったタイミングでDisposeAsync()が実行されます。

次の例を見てみます。

C#
public async Task SampleAsync()
{
Console.WriteLine("開始");

await using (var resource = new AsyncResource())
{
Console.WriteLine("リソース使用中");
}

Console.WriteLine("終了");
}

AsyncResourceDisposeAsync()が次のように実装されているとします。

C#
public class AsyncResource : IAsyncDisposable
{
public async ValueTask DisposeAsync()
{
await Task.Delay(100);
Console.WriteLine("DisposeAsync実行");
}
}

実行順は次のようになります。

開始
リソース使用中
DisposeAsync実行
終了

ポイントは、DisposeAsync()が完了してから終了が出力されることです。

つまり、await usingは単にDisposeAsync()を呼ぶだけでなく、その完了を待ちます。

3-4. try-finallyに展開した場合のイメージ

await usingは、内部的にはtry-finallyに近い形で考えると理解しやすいです。

次のコードがあるとします。

C#
await using (var resource = new AsyncResource())
{
await resource.DoSomethingAsync();
}

これは、イメージとしては次のようなコードに近いです。

C#
var resource = new AsyncResource();

try
{
await resource.DoSomethingAsync();
}
finally
{
if (resource != null)
{
await resource.DisposeAsync();
}
}

実際のコンパイラ変換はより複雑ですが、考え方としてはこのイメージで問題ありません。

つまり、await usingの役割は次の2つです。

  1. スコープを抜けるときに確実に破棄処理を呼ぶ

  2. 破棄処理が非同期なら完了まで待つ

通常のusingtry-finallyに展開されるイメージですが、await usingではfinally内でDisposeAsync()awaitする点が違います。

3-5. await usingを使えるメソッドの条件

await usingを使うには、基本的にawaitが使える場所である必要があります。

つまり、通常はasyncメソッド内で使います。

C#
public async Task ExecuteAsync()
{
await using var resource = new AsyncResource();

await resource.DoSomethingAsync();
}

戻り値は、一般的には次のいずれかになります。

C#
Task
Task<T>
ValueTask
ValueTask<T>

たとえば、次のようなメソッドではawait usingを使えます。

C#
public async Task<string> ReadAsync()
{
await using var resource = new AsyncResource();

return await resource.GetValueAsync();
}

一方、通常の同期メソッドではawaitが使えないため、await usingもそのままでは使えません。

C#
public void Execute()
{
// await using は使えない
}

非同期破棄が必要な型を使う場合は、呼び出し元の設計も非同期にすることを検討しましょう。

4. IAsyncDisposableの使い方

4-1. IAsyncDisposableインターフェイスとは

IAsyncDisposableは、非同期でリソースを解放するためのインターフェイスです。

定義は次のようになっています。

C#
public interface IAsyncDisposable
{
ValueTask DisposeAsync();
}

通常のIDisposableDispose()を持ちます。

C#
public interface IDisposable
{
void Dispose();
}

違いは、IAsyncDisposableDisposeAsync()ValueTaskを返すことです。

これにより、破棄処理の中で非同期処理を行えます。

C#
public async ValueTask DisposeAsync()
{
await CloseConnectionAsync();
}

IAsyncDisposableを実装した型は、await usingで利用できます。

C#
await using var resource = new AsyncResource();

4-2. DisposeAsyncメソッドの実装例

簡単なIAsyncDisposableの実装例を見てみましょう。

C#
public sealed class AsyncResource : IAsyncDisposable
{
private bool _disposed;

public async ValueTask DisposeAsync()
{
if (_disposed)
{
return;
}

await Task.Delay(100);
Console.WriteLine("非同期リソースを解放しました");

_disposed = true;
}
}

このクラスは、DisposeAsync()が呼ばれると非同期で少し待機し、リソース解放のメッセージを出力します。

使用する側は次のように書けます。

C#
public async Task RunAsync()
{
await using var resource = new AsyncResource();

Console.WriteLine("リソースを使用しています");
}

実行結果のイメージは次のとおりです。

リソースを使用しています
非同期リソースを解放しました

await usingを使うことで、呼び出し側はDisposeAsync()を明示的に呼ばなくても、安全に非同期破棄できます。

4-3. ValueTaskを返す理由

DisposeAsync()の戻り値はTaskではなくValueTaskです。

C#
ValueTask DisposeAsync();

ValueTaskは、非同期処理が必ずしも毎回発生しない場合に有効です。

リソース破棄処理では、次のようなケースがあります。

  • すでに破棄済みなので何もしない

  • 非同期処理が不要で即座に完了する

  • 条件によっては非同期I/Oが発生する

  • 一部の処理だけ非同期になる

このように、破棄処理が同期的に完了することも多いため、TaskよりもValueTaskの方が効率的な場合があります。

たとえば、即座に完了する場合は次のように書けます。

C#
public ValueTask DisposeAsync()
{
Console.WriteLine("同期的に完了する破棄処理");
return ValueTask.CompletedTask;
}

一方、非同期処理が必要な場合はasync ValueTaskとして実装できます。

C#
public async ValueTask DisposeAsync()
{
await FlushAsync();
}

ただし、ValueTaskTaskより扱いに注意が必要な場面もあります。通常の利用では、DisposeAsync()の戻り値を複数回awaitしたり保存したりせず、await usingに任せるのが安全です。

4-4. IDisposableとIAsyncDisposableを両方実装するパターン

型によっては、IDisposableIAsyncDisposableの両方を実装することがあります。

C#
public sealed class HybridResource : IDisposable, IAsyncDisposable
{
private bool _disposed;

public void Dispose()
{
if (_disposed)
{
return;
}

Console.WriteLine("同期的にリソースを解放しました");
_disposed = true;
}

public async ValueTask DisposeAsync()
{
if (_disposed)
{
return;
}

await Task.Delay(100);
Console.WriteLine("非同期でリソースを解放しました");
_disposed = true;
}
}

このクラスは、usingでもawait usingでも使えます。

C#
using var syncResource = new HybridResource();

この場合はDispose()が呼ばれます。

C#
await using var asyncResource = new HybridResource();

この場合はDisposeAsync()が呼ばれます。

両方実装する理由は、同期APIと非同期APIの両方に対応するためです。

ただし、両方を実装すると設計が複雑になるため、必要性を考えて実装することが大切です。

4-5. 非同期リソース解放処理を書くときの注意点

DisposeAsync()を書くときには、いくつか注意点があります。

まず、複数回呼ばれても問題ないようにします。

C#
private bool _disposed;

public async ValueTask DisposeAsync()
{
if (_disposed)
{
return;
}

_disposed = true;

await ReleaseAsync();
}

DisposeAsync()は、利用者が明示的に呼ぶ可能性もありますし、await usingで自動的に呼ばれる可能性もあります。そのため、二重解放を防ぐ設計が必要です。

次に、DisposeAsync()内で重い同期処理を行わないようにします。

C#
public async ValueTask DisposeAsync()
{
await FlushAsync();
await CloseAsync();
}

非同期破棄の目的は、I/O待ちを非同期で扱うことです。内部で重い同期処理を実行してしまうと、await usingを使うメリットが薄れてしまいます。

また、DisposeAsync()内で例外が発生する可能性にも注意が必要です。破棄処理で例外が出ると、元の処理で発生した例外を覆い隠してしまう可能性があります。

5. usingとawait usingの違い

5-1. usingはDisposeを同期的に呼び出す

通常のusingは、IDisposable.Dispose()を同期的に呼び出します。

C#
using var resource = new SyncResource();

このコードでは、スコープを抜けるときに次の処理が行われます。

C#
resource.Dispose();

Dispose()は同期メソッドです。

C#
public void Dispose()
{
Console.WriteLine("同期的に破棄");
}

そのため、Dispose()内の処理が完了するまで現在のスレッドは待機します。

ファイルハンドルを閉じる、メモリを解放する、軽い後片付けを行うといった処理であれば、通常のusingで十分です。

5-2. await usingはDisposeAsyncを非同期に待機する

await usingは、IAsyncDisposable.DisposeAsync()を呼び出し、その完了をawaitします。

C#
await using var resource = new AsyncResource();

このコードでは、スコープを抜けるときに次の処理が行われます。

C#
await resource.DisposeAsync();

DisposeAsync()は非同期メソッドとして実装できます。

C#
public async ValueTask DisposeAsync()
{
await Task.Delay(100);
Console.WriteLine("非同期で破棄");
}

このように、破棄処理にI/O待ちや非同期通信が含まれる場合は、await usingを使うことで自然に待機できます。

5-3. IDisposableとIAsyncDisposableの違い

IDisposableIAsyncDisposableの違いを整理すると、次のようになります。

項目IDisposableIAsyncDisposable
メソッドDispose()DisposeAsync()
戻り値voidValueTask
呼び出す構文usingawait using
非同期処理直接awaitできないawaitできる
主な用途同期的なリソース解放非同期のリソース解放

IDisposableは昔からある標準的なリソース解放の仕組みです。

一方、IAsyncDisposableは、非同期処理が必要なリソース解放に対応するための仕組みです。

どちらが優れているというより、用途が異なります。

5-4. 処理の流れをコードで比較

usingawait usingの違いをコードで比較してみましょう。

まず、通常のusingです。

C#
public sealed class SyncResource : IDisposable
{
public void Dispose()
{
Console.WriteLine("Disposeが呼ばれました");
}
}

public void Run()
{
using var resource = new SyncResource();

Console.WriteLine("処理中");
}

実行結果のイメージです。

処理中
Disposeが呼ばれました

次に、await usingです。

C#
public sealed class AsyncResource : IAsyncDisposable
{
public async ValueTask DisposeAsync()
{
await Task.Delay(100);
Console.WriteLine("DisposeAsyncが呼ばれました");
}
}

public async Task RunAsync()
{
await using var resource = new AsyncResource();

Console.WriteLine("処理中");
}

実行結果のイメージです。

処理中
DisposeAsyncが呼ばれました

見た目は似ていますが、呼び出されるメソッドが異なります。

C#
using         Dispose()
await using DisposeAsync()

この違いが、usingawait usingを使い分ける最大のポイントです。

5-5. どちらを使うべきか判断する基準

usingawait usingのどちらを使うべきかは、対象の型がどのインターフェイスを実装しているかで判断します。

対象の型がIDisposableのみを実装している場合は、通常のusingを使います。

C#
using var resource = new SyncResource();

対象の型がIAsyncDisposableを実装しており、非同期で破棄したい場合は、await usingを使います。

C#
await using var resource = new AsyncResource();

両方を実装している場合は、破棄処理を同期的に行いたいか、非同期で行いたいかを考えます。

基本的な判断基準は次のとおりです。

状況使う構文
同期的なリソース解放で十分using
破棄処理で非同期I/Oが発生するawait using
対象がIDisposableのみusing
対象がIAsyncDisposableのみawait using
非同期メソッド内で非同期破棄したいawait using

実務では、利用するクラスのドキュメントや実装を確認し、IAsyncDisposableに対応しているかを見て判断するとよいでしょう。

6. await usingの実践例

6-1. IAsyncDisposableを実装したクラスのサンプル

ここでは、実際にIAsyncDisposableを実装したクラスを作ってみます。

C#
public sealed class SampleAsyncResource : IAsyncDisposable
{
private bool _disposed;

public async Task UseAsync()
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(SampleAsyncResource));
}

Console.WriteLine("リソースを使用しています");
await Task.Delay(100);
}

public async ValueTask DisposeAsync()
{
if (_disposed)
{
return;
}

Console.WriteLine("非同期破棄を開始します");

await Task.Delay(100);

Console.WriteLine("非同期破棄が完了しました");

_disposed = true;
}
}

このクラスには、非同期でリソースを使うUseAsync()と、非同期で破棄するDisposeAsync()があります。

_disposedフラグを使って、破棄済みのリソースが再利用されないようにしています。

6-2. await usingで非同期リソースを安全に破棄する例

先ほどのSampleAsyncResourceawait usingで使ってみます。

C#
public async Task RunAsync()
{
await using var resource = new SampleAsyncResource();

await resource.UseAsync();

Console.WriteLine("メソッドを終了します");
}

実行結果のイメージは次のようになります。

リソースを使用しています
メソッドを終了します
非同期破棄を開始します
非同期破棄が完了しました

RunAsync()メソッドを抜ける直前にDisposeAsync()が呼ばれていることがわかります。

明示的に次のように書く必要はありません。

C#
await resource.DisposeAsync();

await usingを使えば、スコープ終了時に自動で呼び出されます。

6-3. FileStreamやDB接続で使うイメージ

await usingは、実務ではファイルやDB接続のようなI/Oリソースで使われることが多いです。

たとえば、非同期でファイルを書き込む場合です。

C#
public async Task WriteFileAsync()
{
await using var stream = new FileStream(
"sample.txt",
FileMode.Create,
FileAccess.Write,
FileShare.None,
bufferSize: 4096,
useAsync: true);

var bytes = System.Text.Encoding.UTF8.GetBytes("Hello, await using!");

await stream.WriteAsync(bytes, 0, bytes.Length);
}

この例では、streamのスコープ終了時に非同期破棄が行われます。

データベース接続でも同じような考え方ができます。

C#
public async Task ExecuteDbAsync(string connectionString)
{
await using var connection = new SqlConnection(connectionString);

await connection.OpenAsync();

await using var command = connection.CreateCommand();
command.CommandText = "SELECT COUNT(*) FROM Users";

var count = await command.ExecuteScalarAsync();

Console.WriteLine(count);
}

このように、接続やコマンドなどがIAsyncDisposableに対応している場合は、await usingを使うことで非同期に安全な後片付けができます。

6-4. await using varを使った簡潔な書き方

await usingは、ブロック形式だけでなく、await using varとして簡潔に書けます。

C#
await using var resource = new SampleAsyncResource();

await resource.UseAsync();

この書き方では、現在のスコープを抜けるときにDisposeAsync()が呼び出されます。

たとえば、次のようなメソッドがあります。

C#
public async Task ProcessAsync()
{
await using var resource1 = new SampleAsyncResource();
await using var resource2 = new SampleAsyncResource();

await resource1.UseAsync();
await resource2.UseAsync();

Console.WriteLine("処理完了");
}

この場合、ProcessAsync()メソッドを抜けるタイミングでresource1resource2が破棄されます。

コードがすっきりするため、スコープ全体でリソースを使う場合にはawait using varが便利です。

一方で、リソースの使用範囲を明確に限定したい場合は、ブロック形式を使うとよいでしょう。

C#
await using (var resource = new SampleAsyncResource())
{
await resource.UseAsync();
}

6-5. DisposeAsyncが呼ばれる順番を確認する

複数のawait using宣言を使った場合、破棄される順番にも注意が必要です。

通常、同じスコープで複数のリソースを宣言した場合、後に宣言したものから先に破棄されます。

C#
public async Task RunAsync()
{
await using var resource1 = new NamedAsyncResource("Resource 1");
await using var resource2 = new NamedAsyncResource("Resource 2");

Console.WriteLine("処理中");
}

クラスは次のようにします。

C#
public sealed class NamedAsyncResource : IAsyncDisposable
{
private readonly string _name;

public NamedAsyncResource(string name)
{
_name = name;
}

public async ValueTask DisposeAsync()
{
await Task.Delay(100);
Console.WriteLine($"{_name} を破棄しました");
}
}

実行結果のイメージです。

処理中
Resource 2 を破棄しました
Resource 1 を破棄しました

後から作成したリソースが先に破棄されることで、依存関係を安全に扱いやすくなります。

たとえば、connectionの上にcommandがある場合、先にcommandを破棄し、その後でconnectionを破棄する方が自然です。

7. await usingでよくある疑問とエラー

7-1. await usingは何をawaitしているのか

await usingawaitしているのは、リソースの使用処理ではありません。

await usingが待っているのは、スコープ終了時に呼び出されるDisposeAsync()です。

次のコードを見てください。

C#
await using var resource = new AsyncResource();

await resource.DoSomethingAsync();

このうち、await usingawaitは、初期化処理やコンストラクターをawaitしているわけではありません。

イメージとしては、スコープ終了時に次の処理を行っています。

C#
await resource.DisposeAsync();

つまり、await usingawait対象は、破棄処理です。

ここを誤解すると、「await usingを使えばコンストラクターを非同期にできる」と考えてしまいがちですが、それは正しくありません。

7-2. await usingはコンストラクターを非同期化するものではない

C#のコンストラクターはasyncにできません。

C#
public async MyClass()
{
// これはできない
}

await usingを使っても、コンストラクターが非同期になるわけではありません。

次のコードを見てください。

C#
await using var resource = new AsyncResource();

このコードでawaitされるのは、new AsyncResource()ではありません。

new AsyncResource()は通常どおり同期的に実行されます。

awaitされるのは、スコープ終了時のDisposeAsync()です。

非同期の初期化が必要な場合は、ファクトリメソッドを用意することがあります。

C#
public sealed class AsyncResource : IAsyncDisposable
{
private AsyncResource()
{
}

public static async Task<AsyncResource> CreateAsync()
{
var resource = new AsyncResource();

await resource.InitializeAsync();

return resource;
}

private Task InitializeAsync()
{
return Task.Delay(100);
}

public ValueTask DisposeAsync()
{
return ValueTask.CompletedTask;
}
}

使用側は次のようになります。

C#
await using var resource = await AsyncResource.CreateAsync();

この場合、初期化はCreateAsync()awaitし、破棄はawait usingDisposeAsync()を待機します。

7-3. IAsyncDisposableを実装していない型で使えるか

await usingを使うには、基本的に対象の型がIAsyncDisposableを実装している必要があります。

C#
public sealed class AsyncResource : IAsyncDisposable
{
public ValueTask DisposeAsync()
{
return ValueTask.CompletedTask;
}
}

このような型であれば、次のように使えます。

C#
await using var resource = new AsyncResource();

一方、IDisposableだけを実装している型に対してawait usingを使うと、コンパイルエラーになります。

C#
public sealed class SyncResource : IDisposable
{
public void Dispose()
{
}
}

この型に対して次のようには書けません。

C#
await using var resource = new SyncResource(); // エラー

IDisposableの型には通常のusingを使います。

C#
using var resource = new SyncResource();

7-4. DisposeAsyncが呼ばれないケース

await usingを使っていれば、通常はスコープ終了時にDisposeAsync()が呼ばれます。

ただし、次のようなケースには注意が必要です。

まず、リソースをawait usingで管理していない場合です。

C#
var resource = new AsyncResource();

// DisposeAsyncは自動では呼ばれない

この場合、手動で呼び出す必要があります。

C#
await resource.DisposeAsync();

次に、プロセスが強制終了した場合です。アプリケーションがクラッシュしたり、プロセスが強制的に停止されたりした場合、通常のスコープ終了処理が実行されないことがあります。

また、DisposeAsync()を実装していても、使用側がusingを使っている場合はDisposeAsync()ではなくDispose()が呼ばれる可能性があります。両方を実装している型では、どちらの構文を使うかが重要です。

C#
using var resource = new HybridResource();       // Dispose()
await using var asyncResource = new HybridResource(); // DisposeAsync()

非同期破棄を期待する場合は、必ずawait usingを使いましょう。

7-5. ConfigureAwaitとの組み合わせ方

ライブラリ開発などでは、await usingConfigureAwait(false)を組み合わせたい場合があります。

通常のawaitでは、次のように書きます。

C#
await SomeAsync().ConfigureAwait(false);

await usingでも、対象が対応していれば次のように書けます。

C#
await using (resource.ConfigureAwait(false))
{
await resource.DoSomethingAsync().ConfigureAwait(false);
}

また、await using varで使う場合は、書き方に注意が必要です。

C#
await using var resource = new AsyncResource();

この書き方では、DisposeAsync()に対するConfigureAwait(false)を直接見せにくい場合があります。

ライブラリコードでコンテキストのキャプチャを避けたい場合は、ブロック形式でConfigureAwait(false)を使うことを検討します。

C#
var resource = new AsyncResource();

await using (resource.ConfigureAwait(false))
{
await resource.DoSomethingAsync().ConfigureAwait(false);
}

アプリケーションコードでは必ずしも必要ではありませんが、ライブラリ設計では意識しておくとよいポイントです。

7-6. Visual Studioがawait usingを提案する理由

Visual StudioやIDEがawait usingを提案することがあります。

これは、対象の型がIAsyncDisposableを実装しているためです。

たとえば、ある型が次のように定義されているとします。

C#
public sealed class AsyncResource : IAsyncDisposable
{
public ValueTask DisposeAsync()
{
return ValueTask.CompletedTask;
}
}

この型を使っているとき、IDEは「このリソースは非同期破棄できます」と判断し、await usingを提案する場合があります。

特に、非同期メソッド内でIAsyncDisposableを実装した型を使っている場合、DisposeAsync()を確実に呼ぶためにawait usingを使うのが自然です。

IDEの提案は、リソースリークを防ぐためのヒントとして役立ちます。

ただし、すべてのケースで機械的にawait usingへ変更すればよいわけではありません。対象の型がどのような破棄処理を行うのか、同期破棄と非同期破棄のどちらが適切なのかを確認しましょう。

8. await usingを使うときのベストプラクティス

8-1. 非同期破棄が必要な型だけにawait usingを使う

await usingは便利ですが、何でもawait usingにすればよいわけではありません。

基本的には、対象の型がIAsyncDisposableを実装していて、非同期破棄が必要な場合に使います。

C#
await using var resource = new AsyncResource();

同期的な破棄で十分な型には、通常のusingを使います。

C#
using var resource = new SyncResource();

await usingを使うと、スコープ終了時に非同期処理が発生する可能性があります。そのため、不要な場面で使うとコードの意図がわかりにくくなることがあります。

判断に迷った場合は、次の基準で考えるとよいでしょう。

  • 型がIAsyncDisposableを実装しているか

  • 破棄時に非同期I/Oが発生するか

  • 非同期メソッド内で利用しているか

  • ライブラリやフレームワークがawait usingを推奨しているか

8-2. IDisposableとIAsyncDisposableの実装方針を決める

自作クラスでリソース解放を設計する場合、IDisposableIAsyncDisposableのどちらを実装するかを決める必要があります。

同期的なリソース解放だけで十分なら、IDisposableで問題ありません。

C#
public sealed class SyncResource : IDisposable
{
public void Dispose()
{
// 同期的な破棄処理
}
}

非同期I/Oを伴う破棄処理が必要なら、IAsyncDisposableを実装します。

C#
public sealed class AsyncResource : IAsyncDisposable
{
public async ValueTask DisposeAsync()
{
await ReleaseAsync();
}
}

同期APIと非同期APIの両方を提供したい場合は、両方を実装することもあります。

C#
public sealed class Resource : IDisposable, IAsyncDisposable
{
public void Dispose()
{
// 同期破棄
}

public async ValueTask DisposeAsync()
{
// 非同期破棄
await ReleaseAsync();
}
}

ただし、両方を実装する場合は、二重解放や状態管理に注意が必要です。

8-3. 例外発生時でもリソースが解放されるようにする

await usingの大きなメリットは、例外が発生してもDisposeAsync()が呼ばれることです。

C#
public async Task RunAsync()
{
await using var resource = new AsyncResource();

throw new InvalidOperationException();
}

この場合でも、スコープを抜けるときにDisposeAsync()が実行されます。

これは、await usingtry-finallyに近い仕組みで動作するためです。

ただし、DisposeAsync()内でも例外が発生する可能性があります。

C#
public async ValueTask DisposeAsync()
{
await CloseAsync(); // ここで例外が発生する可能性
}

破棄処理で例外が発生すると、元の例外処理に影響することがあります。

そのため、DisposeAsync()内では、必要に応じて例外をログに記録したり、再スローすべきかを慎重に判断したりすることが重要です。

8-4. DisposeAsync内で重い同期処理を避ける

DisposeAsync()の中で重い同期処理を行うと、非同期破棄のメリットが失われます。

避けたい例は次のようなコードです。

C#
public ValueTask DisposeAsync()
{
Thread.Sleep(5000);
return ValueTask.CompletedTask;
}

このコードは非同期メソッドのように見えますが、実際にはスレッドをブロックしています。

非同期破棄では、I/O待ちをawaitするように書きます。

C#
public async ValueTask DisposeAsync()
{
await FlushAsync();
await CloseAsync();
}

また、CPU負荷の高い処理をDisposeAsync()に詰め込みすぎるのも避けるべきです。

DisposeAsync()は、あくまでリソース解放のためのメソッドです。通常の処理ロジックや重い計算処理を含めると、コードの見通しが悪くなります。

8-5. ライブラリ設計でIAsyncDisposableを採用すべきケース

ライブラリや共通部品を設計する場合、次のような型ではIAsyncDisposableの採用を検討するとよいでしょう。

  • 非同期ストリームを内部で保持する型

  • データベース接続やトランザクションを扱う型

  • ネットワーク接続を管理する型

  • 外部APIクライアントを管理する型

  • メッセージキューやソケットを扱う型

  • 破棄時にフラッシュや送信完了待ちが必要な型

たとえば、内部で非同期ストリームを保持している場合は、外側のクラスもIAsyncDisposableを実装するのが自然です。

C#
public sealed class MyWriter : IAsyncDisposable
{
private readonly Stream _stream;

public MyWriter(Stream stream)
{
_stream = stream;
}

public async ValueTask DisposeAsync()
{
await _stream.DisposeAsync();
}
}

このようにすることで、利用者は次のように書けます。

C#
await using var writer = new MyWriter(stream);

ライブラリ利用者にとって、await usingで安全にリソースを解放できる設計は扱いやすいです。

まとめ

await usingは、C#で非同期にリソースを破棄するための構文です。

通常のusingDispose()を呼び出すのに対して、await usingDisposeAsync()を呼び出し、その完了をawaitします。

C#
using         Dispose()
await using DisposeAsync()

await usingを使うには、対象の型が基本的にIAsyncDisposableを実装している必要があります。

C#
public interface IAsyncDisposable
{
ValueTask DisposeAsync();
}

非同期破棄が必要になるのは、ファイル、ネットワーク、データベース接続、ストリームなど、I/Oを伴うリソースを扱う場面です。

await usingを使うことで、次のようなメリットがあります。

メリット内容
非同期で破棄できるDisposeAsync()をawaitできる
例外時も安全スコープ終了時に破棄される
コードが簡潔手動でDisposeAsync()を書く必要がない
I/O待ちに強いスレッドをブロックしにくい

一方で、同期的なリソース解放で十分な型には、通常のusingを使えば問題ありません。

C#
using var resource = new SyncResource();

非同期破棄が必要な型には、await usingを使います。

C#
await using var resource = new AsyncResource();

C#のawait usingを理解するポイントは、難しく考えすぎず、まずは次の一文で押さえることです。

usingはDisposeを呼び、await usingはDisposeAsyncを呼ぶ。

この違いを理解しておけば、IAsyncDisposableを実装した型や、非同期リソースを扱うコードをより安全に書けるようになります。