非同期ストリームの提案の一部は、リソースの非同期dispose機能である。このインターフェイスはIAsyncDisposable
と呼ばれ、DisposeAsync
と呼ばれる1つのメソッドを持つ。最初に気づくことは命名規約だろう。歴史的にMicrosoftのガイダンスでは非同期メソッドは最後に“Async”接尾辞を付けるべきとされてきた。対照的に(例えばクラスやインターフェイスなどの)型の推奨は、“Async”を接頭辞に使ってきた。
パフォーマンス改善のポテンシャルとして、DisposeAsync
は通常のTaskオブジェクトの代わりにValueTaskが返される。不安定になる可能性があるため、DisposeAsync
メソッドにキャンセルトークンを渡すことができない。
IDisposable vs IAsyncDisposable
IAsyncDisposable
はIDisposable
を継承していないため、開発者は1つまたは両方を実装することを選択できる。現在のセオリーでは、両方のインターフェイスを提供するクラスはレアである。
Dispose
とDisposeAsync
の両方を持つクラスは、どちらの順で呼ばれてもよいようにすることを
Microsoftは推奨する。 最初の呼び出しのみが受け入れられ、その後の呼び出しはなにもしない。(これは推奨であり、特定の実装では異なる場合もある)
非同期Disposableクラス
.NET Core 3.0では非同期破棄をサポートする必要があるとして、いくつかのクラスが選出されている。
一つ目はStream
である。このベースクラスは様々なシナリオで使われ、サードパーティーのライブラリでサブクラス化されている。AsyncDispose
のデフォルト実装は、別スレッドでDispose
を呼び出す。これは、一般的に悪いプラクティスであると考えられており、サブクラスはより適切なふるまいでこれをオーバーライドするべきである。
同じようにBinaryReader
やTextReader
は、オーバーライドがなければDispose
を別スレッドで呼び出す。
.NET Core 3.0の一部として、Threading.TimerはすでにIAsyncDisposableを実装している
CancellationTokenRegistration:
CancellationTokenRegistration.Disposeには2つのことがある: コールバックの登録を解除し、実行中のコールバックがあれば、完了するまでブロックする。DisposeAsyncでも同じことができるが、同期ではなく非同期で待つことができる。
IAsyncEnumeratorの変更
IAsyncEnumerator
の最新のレポートでは、MoveNextAsync
メソッドもValueTask<bool>
を返すように変更される。これはMoveNextAsync
によるタイトなループでパフォーマンスを向上でき、ほとんどの場合、同期的に戻ることになる。
IAsyncEnumerator
の代替デザイン
IAsyncEnumerator
の代替デザインが検討された。IEumerator
のようなCurrentプロパティではなく、以下のシグニチャのメソッドを公開する。
T TryGetNext(out bool success);
このsuccessパラメータは同期的に読み取ることができるかどうかを示す。この値がfalseの場合、関数の結果は捨てられ、その後のデータを待つためにはMoveNextAsync
が呼ばれることになる。
通常、メソッドはBooleanを返そうとして実際の値はoutパラメータにある。しかしメソッドは非共変(例えばIAsyncEnumerator<out T>
を使うことができない)のため、標準でない順序付けが必要であった。
このデザインは2つの理由で保留になった。1つ目は、テスト中のパフォーマンスの利点は魅力的ではなかった。このパターンを使うことでパフォーマンスは改善するが、現実の使い方での改善はごくわずかである。さらにこのパターンを、ライブラリの作者とクライアントの両方の観点から、正しく使うことは困難である。
パフォーマンスの利点が後日、重要であると判断された場合は、オプションで2番目のインターフェイスを追加できる。
Cancellation TokensとAsync Enumeration
IAsyncEnumerable<T>
/IAsyncEnumerator<T>
はキャンセルに関知しないと決定した。つまりキャンセルトークンを受け入れることができず、新しいasync対応のfor-each構文を直接使用する方法がない。
これはキャンセルトークンを使用できないという意味ではなく、あなたを助ける特別な構文がないということである。ThrowIfCancellationRequested
を明示的に呼び出すか、イテレータにキャンセルトークンを渡すことで可能だ。
For-eachループ
For-eachループは同期インターフェイスを使用する。非同期列挙子を使用するためには以下の構文を使用する:
await foreach (var i in enumerable)
awaitキーワードの位置に注意が必要だ。もし以下の位置に移動してしまったら、コレクションの取得は非同期になるが、for-eachループ自身が非同期になる。
foreach (var i in await enumerable)
通常のfor-eachループのように列挙子インターフェイスを実際に実装する必要はない。非同期for-eachパターンにマッチするようにインスタンス(または拡張)メソッドを公開すると、インターフェイスが提供されていたとしても、そちらが使用される。