.NET 6でのAPIの100を超える変更の中には、非同期コードの利用をより簡単かつ安全にするために設計されたいくつかの機能がある。ハイライトをいくつか紹介する。
新しいWaitAsyncメソッド
理想は、すべての非同期機能が操作をキャンセルする機能を提供することであるが、提供されない場合もある。そのために、3つの新しいWaitAsyncメソッドがTask
とTask<TResult>
に追加された。
public Task WaitAsync(CancellationToken cancellationToken);
public Task WaitAsync(TimeSpan timeout);
public Task WaitAsync(TimeSpan timeout, CancellationToken cancellationToken);
このシグネチャーが示すように、これらのメソッドはタイムアウトまたはCancellationToken
、あるいはその両方を引数として受ける。これらのいずれかを使用して、非同期操作の完了待ちを中止できる。これは、操作自体を中止することと同じではないことに注意してください。呼び出し元のコードが完了を待たなくなったとしても操作は続行される場合がある。
ちょっとした雑学だが、Microsoftはintミリ秒の形式のタイムアウト値を間違いと見なすようになった。今後、すべての期間ベースのタイムアウトは、TimeSpan
の観点からのみ表現する必要がある。
再利用可能なCancellationTokenSource
Webリクエストなどの外部でトリガーされる操作が開始されると、多くの場合、CancellationTokenSource
を作成する必要がある。これにより、リクエスターがリクエストをキャンセルしたり、接続が切断されたりした場合に、リクエストハンドラーを中止できる。ほとんどの場合、リクエストはキャンセルされない。つまり、CancellationTokenSource
は起動されない。
パフォーマンスを向上させるために、フレームワーク開発者は、CancellationTokenSourceを新しい操作に再利用したいと考えている。しかし、現在、リンクされている古いCancellationToken
を何が保持しているのかを知る術がないため、再利用できない。操作がキャンセルされると、奇妙なエラーが発生する可能性がある。リサイクルされたCancellationTokenSourceをまったく別の操作と共有するためである。
新しいCancellationTokenSource.TryReset操作は、この問題を修正している。すべての古いCancellationToken
オブジェクトをCancellationTokenSource
から切断する。このように、新しい操作をキャンセルしても、前の操作に影響を与えることはできません。古いCancellationToken
オブジェクトはそれでも存在するが、リサイクルされたCancellationTokenSource
からメッセージを受信することはない。
これは、起動しないCancellationTokenSource
でのみ機能するため、TryReset
と呼ばれる。CancellationTokenSource
が実際にキャンセルされると、リサイクルされない場合がある。そのため、使用パターンは次のようになる。
if (!cts.TryReset())
cts = new CancellationTokenSource();
キャンセルイベント
いつキャンセルが要求されたかを検出する方法の1つは、CancellationToken.Register
を呼び出して、それに呼び出す権限を委譲することである。まれに、この委譲は、使用された元のCancellationToken
にアクセスする必要がある。
CancellationToken.Registerの新しいオーバーロードは、次の機能を提供する。
public CancellationTokenRegistration Register<T>(Action<T, CancellationToken> callback, T state);
public CancellationTokenRegistration UnsafeRegister<T>(Action<T, CancellationToken> callback, T state);
UnsafeRegisterのドキュメントは完全ではないが、古いバグレポートのコメントには次のように書かれている。
CancellationToken.Register
は、現在のExecutionContext
をキャプチャし、呼び出された場合にコールバックを呼び出すためにそれを使用する。これは一般的に望ましいことであり、正しいデフォルト動作である。しかし、コールバックがECを気にしないことが確実にわかっている場合(たとえば、サードパーティのコードを呼び出さない)、代わりにUnsafeRegisterを使用できる(3.0で新しく追加された)。UnsafeRegister
は、Capture
がnull
を返したかのように、ExecutionContext
のキャプチャをスキップする。
より簡単な実行コンテキストの復元
Ben Adams氏によると、「それ自体が非同期ではないメソッドを返すタスクを待つことは、AsyncLocal/ExecutionContext
の変更を元に戻すことを保証するものではない」。その結果、診断が難しいバグが発生する可能性がある。この例では、Propagatedヘッダーが以前の非同期ミドルウェアなしで混同され、Kestrelが、AsyncLocalによる同じHTTP/1.1接続上のリクエスト間でのコンテキストデータ流出を引き起こす。
ExecutionContext.Capture
を新しいExecutionContext.Restore
関数と組み合わせることで、この問題を回避できる。
このシリーズの以前のレポートは、以下のリンクを参照してください。