クラスCancellationTokenSource
は使い捨てです。 Reflectorをざっと見てみると、(おそらく)非管理対象リソースであるKernelEvent
の使用が証明されています。 CancellationTokenSource
にはファイナライザがないため、破棄しない場合、GCは処理しません。
一方、MSDNの記事 Managed Threadsでのキャンセル にリストされているサンプルを見ると、1つのコードスニペットのみがトークンを破棄しています。
コードでそれを処分する適切な方法は何ですか?
using
でラップできません。そして、待機しない場合にのみキャンセルするのは理にかなっています。ContinueWith
呼び出しを使用してタスクにDispose
を追加できますが、その方法はありますか?.ForAll(x => Console.Write(x))
としましょうか?Reset
およびIsCancelRequested
フィールドをクリーンアップするToken
メソッドのようなものがないため、タスクを開始するたびに(または、 PLINQクエリ)新しいクエリを作成する必要があります。本当ですか?はいの場合、私の質問は、これらの多くのDispose
インスタンスでCancellationTokenSource
を処理するための正しい推奨される戦略は何ですか?
CancellationTokenSource
でDisposeを呼び出す必要があるかどうかを話すと、プロジェクトでメモリリークが発生し、CancellationTokenSource
が問題であることがわかりました。
私のプロジェクトには、データベースを常に読み取り、さまざまなタスクを実行するサービスがあります。また、リンクされたキャンセルトークンをワーカーに渡していたため、データの処理が完了した後でもキャンセルトークンが破棄されず、メモリリークが発生しました。
MSDN マネージスレッドのキャンセル 明確に述べています:
終了したら、リンクされたトークンソースで
Dispose
を呼び出す必要があることに注意してください。より完全な例については、「 方法:複数のキャンセルリクエストをリッスンする 」を参照してください。
私の実装ではContinueWith
を使用しました。
現在の答えはどれも満足できるものではないと思いました。調査した後、Stephen Toubからのこの返信を見つけました( reference ):
場合によります。 .NET 4では、CTS.Disposeは2つの主な目的を果たしました。 CancellationTokenのWaitHandleがアクセスされた場合(したがって、遅延的に割り当てられた場合)、Disposeはそのハンドルを破棄します。さらに、CTSがCreateLinkedTokenSourceメソッドを介して作成された場合、DisposeはリンクされたトークンからCTSのリンクを解除します。 .NET 4.5では、Disposeには追加の目的があります。CTSがカバーの下でタイマーを使用する場合(CancelAfterが呼び出された場合など)、タイマーは破棄されます。
CancellationToken.WaitHandleが使用されることは非常にまれであるため、通常、その後のクリーンアップはDisposeを使用する大きな理由にはなりません。 ただし、CreateLinkedTokenSourceを使用してCTSを作成している場合、またはCTSのタイマー機能を使用している場合は、Disposeを使用する方がインパクトがあります。
私が思う大胆な部分は重要な部分です。彼は「より影響力の強い」を使用しているため、少しあいまいです。これらの状況でDispose
を呼び出すことを意味すると解釈していますが、そうでない場合はDispose
を使用する必要はありません。
ILSpyでCancellationTokenSource
を確認しましたが、実際にはManualResetEvent
であるm_KernelEventしか見つかりません。これはWaitHandleオブジェクトのラッパークラスです。これはGCによって適切に処理される必要があります。
常にCancellationTokenSource
を破棄する必要があります。
廃棄方法は、シナリオによって異なります。いくつかの異なるシナリオを提案します。
using
は、待機中の並列作業でCancellationTokenSource
を使用している場合にのみ機能します。それがあなたのセナリオなら、それは素晴らしい、それは最も簡単な方法です。
タスクを使用するときは、ContinueWith
を破棄するように指定したCancellationTokenSource
タスクを使用します。
Plinqでは、using
を使用できます。これは、並列実行しているが、並列実行中のすべてのワーカーが終了するのを待っているためです。
UIの場合、単一のキャンセルトリガーに関連付けられていないキャンセル可能な操作ごとに新しいCancellationTokenSource
を作成できます。 List<IDisposable>
そして、各ソースをリストに追加し、コンポーネントが破棄されるときにそれらすべてを破棄します。
スレッドの場合、すべてのワーカースレッドを結合し、すべてのワーカースレッドが終了したら単一のソースを閉じる新しいスレッドを作成します。 CancellationTokenSource、いつ廃棄するか? を参照してください。
常に方法があります。 IDisposable
インスタンスは常に破棄する必要があります。サンプルは、コアの使用法を示すための簡単なサンプルであるため、またはサンプルのデモを行うクラスのすべての側面を追加するのが非常に複雑であるため、しばしばそうではありません。サンプルは単なるサンプルであり、必ずしも(または通常でも)生産品質のコードではありません。すべてのサンプルがそのまま製品コードにコピーできるわけではありません。
この答えはまだGoogle検索で出てきており、投票された答えは完全なストーリーを提供するものではないと思います。 ソースコード for CancellationTokenSource
(CTS)およびCancellationToken
(CT)を見た後、ほとんどのユースケースで次のコードシーケンスが適切であると考えています。
if (cancelTokenSource != null)
{
cancelTokenSource.Cancel();
cancelTokenSource.Dispose();
cancelTokenSource = null;
}
m_kernelHandle
上記の内部フィールドは、CTSクラスとCTクラスの両方でWaitHandle
プロパティをサポートする同期オブジェクトです。そのプロパティにアクセスした場合にのみインスタンス化されます。したがって、WaitHandle
を使用してTask
を使用している場合を除き、Dispose
を呼び出してdisposeを呼び出しても効果はありません。
もちろん、areを使用している場合、上記の他の回答で提案されていることを実行し、WaitHandle
の呼び出しを_(VARIABLE WaitHandleのWindows APIドキュメント で説明されているように、結果は未定義であるため、ハンドルを使用した操作は完了します。
私がこれを尋ねて多くの有益な答えを得てから長い時間が経ちましたが、これに関連する興味深い問題に遭遇し、別の種類の答えとしてここに投稿しようと思いました:
CancellationTokenSource.Dispose()
は、CTSのToken
プロパティを取得しようとする人がいないことが確実な場合にのみ呼び出す必要があります。それ以外の場合は、レースであるため、notと呼ぶ必要があります。たとえば、こちらをご覧ください。
https://github.com/aspnet/AspNetKatana/issues/108
この問題の修正では、以前にcts.Cancel(); cts.Dispose();
を実行していたコードが、キャンセル状態を監視するためにキャンセルトークンを取得しようとするほど運が悪かったためにcts.Cancel();
を実行するように編集されましたafterDisposeが呼び出された場合、残念ながらObjectDisposedException
に加えて、計画していたOperationCanceledException
も処理する必要があります。 。
この修正に関連するもう1つの重要な観察は、Tratcherによるものです。「破棄は、キャンセルがすべて同じクリーンアップを行うため、キャンセルされないトークンに対してのみ必要です。」つまり、破棄する代わりにCancel()
するだけで十分です!