質問: TokenSource.Token; token.Register(() => AsyncCallback()); Task.Run(() => CheckController.Execute(token, this)); } ``` いまはこのようなメソッドで止めているのですが、これだと止まったかどうかが判定できず、その後の動作によってはエラーが発生してしまいます。 `StartCheck()` を呼ぶ際にコールバック関数を設定したり、`StopCheck()` を実行した後にコントローラーから返答があればいいのですが、何かいい案があれば教えていただきたいです。 ``` private void StopCheck() { this.TokenSource.Cancel(); } ```
回答: 「別のコントローラー(のループメソッド)」を変更して良いのなら、コールバック関数を指定するパラメータを増やして、ループメソッドの最終処理でコールバック関数を呼び出すようにすれば、終了タイミングを知ることが出来ると思われます。 以下の記事の後半「メモ」の部分ですね。抜粋しておきます。 C#のawait/async非同期処理およびラムダ式 > > 以下の場合、connectAsyncを実行すると内部で非同期処理が実行されて、その処理が終了するとcallbackが実行される。 > > callbackは同期関数なので、内部で非同期処理をawaitできない。非同期処理をそのまま非同期でコールするか、Wait()で待つしかない。もしawaitを使いたいなら、callbackの型をFuncにする。(そうしないといけないケースがあるのか?) > > > > ``` > private void Button_Click(object sender, RoutedEventArgs e) { > Debug.WriteLine( "--- start ---" ); > Task t = connectAsync( "abc", (int stat) => { > Debug.WriteLine( "start lambda" ); > Debug.WriteLine( "stat={0}", stat ); > Task.Delay(1000).Wait(); // 終了までこのまま処理を待つ > //Task.Delay(1000); // 非同期で実行されるので、即材に終了する > Debug.WriteLine( "end lambda" ); > return 2; > } ); > Debug.WriteLine( "--- end ---" ); > } > > private async Task connectAsync(string url, Func callback) { > Debug.WriteLine( "start connectAsync" ); > int stat = 1; > await Task.Delay( 2000 ); > int ret = callback(stat); > Debug.WriteLine( "ret={0}", ret ); > Debug.WriteLine( "end connectAsy > > ``` > > 古い形式らしい同様の内容はこちら。 コールバック: 非同期処理の終了通知 「別のコントローラー(のループメソッド)」が変更出来ないのなら以下になるでしょうか。 **基本的には`Task.Run(() => CheckController.Execute(token, this));`で作成・起動された`Task`のインスタンスが作りっぱなしであってアプリケーションクラスの中に保持されていないことが問題でしょう。** アプリケーションのどこかに`Task t;`と定義しておいて、`t = Task.Run(() => CheckController...`として作成・起動時に代入しておけば、`t.Status`とか`t.IsCompleted`とかで状態を知ることが出来ます。(eventを受け取るのは無理のようですが) ただし、CancelされてもCanceledではなくRanToCompletionの場合が多いようですね。 Task.Status プロパティ TaskStatus 列挙型 > > > ``` > Canceled 6 タスクの CancellationToken がシグナル状態であるときに、タスクがこのトークンを使用して OperationCanceledException をスローすることによって取り消しを受領したか、タスクの実行開始前にタスクの CancellationToken が既にシグナル状態でした。 詳細については、「タスクのキャンセル」をご覧ください。 > Created 0 タスクは初期化されていますが、まだスケジュールされていません。 > Faulted 7 タスクはハンドルされない例外が発生したために終了しました。 > RanToCompletion 5 タスクの実行は正常に完了しました。 > Running 3 タスクは実行中で、まだ完了していません。 > WaitingForActivation 1 タスクはアクティブ化されるのを待機中で、.NET Framework インフラストラクチャによって内部的にスケジュールされています。 > WaitingForChildrenToComplete 4 タスクは実行を終了し、アタッチされている子タスクの完了を暗黙的に待機しています。 > WaitingToRun 2 タスクの実行はスケジュールされていますが、まだ開始されていません。 > > ``` > > Task.IsCompleted プロパティ > > タスクが完了した (つまり、RanToCompletion、Faulted、Canceled) のいずれかの最終状態であることを`true`します。それ以外の場合は、`false`ます。 > > > なお、Task.Wait メソッド や Task.ContinueWith メソッド あたりが使えそうな記事も見かけましたが、単純なCancel後のWaitは上手くいきませんでした。使い方に工夫が必要なのかもしれません。 上記も含めてこの辺の記事とその関連ページがそれらの話題を扱っていて参考になると思われます。 色々な方法があるようなので、現在行っていることおよびやりたいことと整合性のある方法を選択して試してみてください。 タスク ベースの非同期プログラミング タスクのキャンセル > > キャンセル処理が正常に実行されるためには、要求コードが CancellationTokenSource.Cancel メソッドを呼び出し、ユーザー デリゲートが操作を適時に終了する必要があります。 次のオプションのいずれかを使用して操作を終了できます。 > > > * デリゲートから戻ります。 多くの場合、この処理で十分ですが、この方法で取り消されたタスク インスタンスは、 TaskStatus.RanToCompletion 状態ではなく、 TaskStatus.Canceled 状態に遷移します。 > * OperationCanceledException をスローし、これをキャンセルが要求されたトークンに渡します。 これを行うには、 ThrowIfCancellationRequested メソッドを使用する方法をお勧めします。 この方法で取り消されたタスクは Canceled 状態に遷移し、タスクがキャンセル要求に応答したことを確認するために呼び出し元のコードによって使用されます。 > > > 方法: タスクとその子を取り消す > > 以下の例では、次のタスクを実行する方法を説明します。 > > > 1. 取り消すことができるタスクを作成し、開始します。 > 2. キャンセル トークンをユーザー デリゲートに渡し、必要に応じてタスク インスタンスにも渡します。 > 3. ユーザー デリゲート内のキャンセル要求を確認し、これに応答します。 > 4. 必要に応じて、タスクが取り消された呼び出し元のスレッドを確認します。 > > > 呼び出し元のスレッドは、タスクを強制終了せず、キャンセルが要求されたことを通知するだけです。 タスクが既に実行中である場合、ユーザー デリゲートが要求を確認して適切に応答します。 タスクを実行する前にキャンセルが要求された場合、ユーザー デリゲートは実行されず、タスク オブジェクトは Canceled 状態に遷移します。 > > > マネージド スレッドのキャンセル > > 連携によるキャンセル処理モデルを実装するための一般的なパターンは次のとおりです。 > > > * CancellationTokenSource オブジェクトのインスタンスを作成します。このオブジェクトでは、個々のキャンセル トークンへのキャンセル通知を管理し、送信します。 > * CancellationTokenSource.Token プロパティによって返されるトークンを、キャンセルをリッスンしているそれぞれのタスクまたはスレッドに渡します。 > * それぞれのタスクまたはスレッドに対し、キャンセルに応答するメカニズムを提供します。 > * キャンセルの通知を提供する CancellationTokenSource.Cancel メソッドを呼び出します。 > > > 操作のキャンセルとオブジェクトのキャンセル キャンセル要求のリッスンと応答 ポーリングによるリッスン > > ループや再帰を伴う長時間にわたる計算では、CancellationToken.IsCancellationRequested プロパティの値を定期的にポーリングすることによってキャンセル要求をリッスンできます。 > > > コールバックの登録によるリッスン > > 操作によっては、キャンセル トークンの値を定期的に確認できないことによりブロックされる場合があります。 そのような場合は、キャンセル要求を受け取ったときにメソッドのブロックを解除するコールバック メソッドを登録できます。 > > > 待機ハンドルを使用したリッスン > > System.Threading.ManualResetEvent や System.Threading.Semaphore などの同期プリミティブを待機している間、キャンセル可能な操作をブロックできる場合は、CancellationToken.WaitHandle プロパティを使用してその操作がイベントとキャンセル要求の両方を待機するようにできます。 > > > 方法: キャンセル要求のコールバックを登録する > > 次の例では、トークンを作成したオブジェクトに対する Cancel の呼び出しにより IsCancellationRequested プロパティが true になったときに呼び出されるデリゲートを登録する方法を示します。 > > > > > 次の例では、CancelAsync メソッドを、キャンセル トークンを通じてキャンセルが要求されたときに呼び出されるメソッドとして登録します。 > > >