BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ ニュース IAsyncDisposableとIAsyncEnumeratorの更新

IAsyncDisposableとIAsyncEnumeratorの更新

原文(投稿日:2019/01/31)へのリンク

非同期ストリームの提案の一部は、リソースの非同期dispose機能である。このインターフェイスはIAsyncDisposableと呼ばれ、DisposeAsyncと呼ばれる1つのメソッドを持つ。最初に気づくことは命名規約だろう。歴史的にMicrosoftのガイダンスでは非同期メソッドは最後に“Async”接尾辞を付けるべきとされてきた。対照的に(例えばクラスやインターフェイスなどの)型の推奨は、“Async”を接頭辞に使ってきた。

パフォーマンス改善のポテンシャルとして、DisposeAsyncは通常のTaskオブジェクトの代わりにValueTaskが返される。不安定になる可能性があるため、DisposeAsyncメソッドにキャンセルトークンを渡すことができない。

IDisposable vs IAsyncDisposable

IAsyncDisposableIDisposableを継承していないため、開発者は1つまたは両方を実装することを選択できる。現在のセオリーでは、両方のインターフェイスを提供するクラスはレアである。

DisposeDisposeAsyncの両方を持つクラスは、どちらの順で呼ばれてもよいようにすることを

Microsoftは推奨する。 最初の呼び出しのみが受け入れられ、その後の呼び出しはなにもしない。(これは推奨であり、特定の実装では異なる場合もある)

非同期Disposableクラス

.NET Core 3.0では非同期破棄をサポートする必要があるとして、いくつかのクラスが選出されている。

一つ目はStreamである。このベースクラスは様々なシナリオで使われ、サードパーティーのライブラリでサブクラス化されている。AsyncDisposeのデフォルト実装は、別スレッドでDisposeを呼び出す。これは、一般的に悪いプラクティスであると考えられており、サブクラスはより適切なふるまいでこれをオーバーライドするべきである。

同じようにBinaryReaderTextReaderは、オーバーライドがなければ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パターンにマッチするようにインスタンス(または拡張)メソッドを公開すると、インターフェイスが提供されていたとしても、そちらが使用される。

この記事に星をつける

おすすめ度
スタイル

BT