への呼び出しが返されない状況がありCancellationTokenSource.Cancel
ます。代わりに、Cancel
が呼び出された後 (および戻る前) に、キャンセルされるコードのキャンセル コードで実行が続行されます。キャンセルされたコードが待機可能なコードをその後呼び出さない場合、最初に呼び出した呼び出し元Cancel
は制御を取り戻すことはありません。これは非常に奇妙です。Cancel
キャンセルのリクエストを記録するだけで、キャンセル自体に関係なくすぐに返信できると思います。Cancel
が呼び出されているスレッドが、キャンセルされている操作に属するコードを実行することになり、呼び出し元に戻る前に実行されるという事実Cancel
は、フレームワークのバグのように見えます。
これがどのようになるかは次のとおりです。
コードの一部があります。これを「ワーカー コード」と呼びましょう。これは、何らかの非同期コードを待機しています。簡単にするために、このコードが Task.Delay で待機しているとしましょう。
try { await Task.Delay(5000, cancellationToken); // … } catch (OperationCanceledException) { // …. }
「ワーカー コード」が呼び出される直前にTask.Delay
、スレッド T1 で実行されます。継続 (「await」の後の行または catch 内のブロック) は、一連の要因に応じて、後で T1 または他のスレッドで実行されます。
- もう 1 つのコードがあります。これを「クライアント コード」と呼びましょう
Task.Delay
。このコードは を呼び出しますcancellationToken.Cancel
。への呼び出しCancel
は、スレッド T2 で行われます。
の呼び出し元に戻ることで、スレッド T2 が続行することを期待しますCancel
。catch (OperationCanceledException)
また、スレッド T1 または T2 以外のスレッドですぐに実行されるコンテンツが表示されることを期待しています。
次に何が起こるかは驚くべきことです。スレッド T2 では、Cancel
が呼び出された後、 内のブロックですぐに実行が続行されることがわかりcatch (OperationCanceledException)
ます。そして、それCancel
はまだコールスタックにある間に起こります。への呼び出しCancel
がキャンセルされたコードによってハイジャックされたかのようです。このコール スタックを示す Visual Studio のスクリーンショットを次に示します。
より多くのコンテキスト
実際のコードが何をするかについて、もう少し説明があります。リクエストを蓄積する「ワーカー コード」があります。リクエストは、いくつかの「クライアント コード」によって送信されています。数秒ごとに「ワーカー コード」がこれらのリクエストを処理します。処理された要求はキューから削除されます。ただし、「クライアント コード」は、要求をすぐに処理する必要があるポイントに達したと判断することがあります。これを「ワーカー コード」に伝えるために、「Jolt
ワーカー コード」が提供するメソッドを呼び出します。Jolt
「クライアント コード」によって呼び出されるメソッドはTask.Delay
、ワーカー コードのメイン ループによって実行される a をキャンセルすることによって、この機能を実装します。ワーカーのコードはTask.Delay
キャンセルされ、既にキューに入れられていたリクエストの処理に進みます。
実際のコードは最も単純な形式に分解されており、コードはGitHubで入手できます。
環境
この問題は、コンソール アプリ、Universal Apps for Windows のバックグラウンド エージェント、および Universal Apps for Windows Phone 8.1 のバックグラウンド エージェントで再現できます。
この問題は、コードが期待どおりに機能し、への呼び出しCancel
がすぐに返される Windows 用のユニバーサル アプリでは再現できません。