CancellationTokenがCancellationTokenSourceと分離しているのはなぜですか?


137

クラスCancellationTokenに加えて.NET 構造体が導入された理由の根拠を探していますCancellationTokenSource。APIの使用方法は理解ていますが、そのように設計されている理由も理解したいと思います。

すなわち、なぜ私たちは持っているのですか?

var cts = new CancellationTokenSource();
SomeCancellableOperation(cts.Token);

...
public void SomeCancellableOperation(CancellationToken token) {
    ...
    token.ThrowIfCancellationRequested();
    ...
}

次のように直接渡す代わりにCancellationTokenSource

var cts = new CancellationTokenSource();
SomeCancellableOperation(cts);

...
public void SomeCancellableOperation(CancellationTokenSource cts) {
    ...
    cts.ThrowIfCancellationRequested();
    ...
}

これは、トークンを渡すよりもキャンセル状態のチェックが頻繁に行われるという事実に基づくパフォーマンス最適化ですか?

それで、CancellationTokenSourceを追跡して更新できCancellationTokens、各トークンのキャンセルチェックはローカルフィールドアクセスですか?

どちらの場合も、ロックされていない揮発性のブールで十分であることを考えると、なぜそれが速いのかはまだわかりません。

ありがとう!

回答:


110

これらのクラスの設計と実装に携わっていました。

短い答えは「懸念の分離」です。さまざまな実装戦略があり、少なくとも型システムと初期学習に関してはより単純なものがあるのは確かです。ただし、CTSとCTは非常に多くのシナリオ(ディープライブラリスタック、並列計算、非同期など)での使用を目的としているため、多くの複雑な使用例を考慮して設計されています。これは、パフォーマンスを犠牲にすることなく、成功するパターンを促進し、アンチパターンを阻止することを目的とした設計です。

APIの誤動作のためにドアが開いたままになっていると、キャンセルデザインの有用性がすぐに損なわれる可能性があります。

CancellationTokenSource ==「キャンセルトリガー」、およびリンクされたリスナーを生成

CancellationToken ==「キャンセルリスナー」


7
どうもありがとう!内部の知識は本当に感謝しています。
Andrey Tarantsov 2013年

stackoverflow.com/questions/39077497/…StreamSocketの inputstreamのデフォルトのタイムアウトはどうあるべきか考えていますか?canceltokenを使用して読み取り操作をキャンセルすると、関連するソケットも閉じます。この問題を克服する方法はありますか?
sam18

@マイク-気になるところ:CTのようにCTSでThrowIfCancellationRequested()のようなものを呼び出せないのはなぜですか?
rory.ap 2017年

それらにはさまざまな責任があり、cts.Token.ThrowIfCancellationRequested()を書くにはそれほど冗長ではないので、CTSにリスナーAPIを直接追加しませんでした。
マイク・リデル

@Mikeなぜcanceltokenは構造体でクラスではないのですか?パフォーマンスの利点は見当たらない
Neir0

85

正確な質問があり、このデザインの背後にある理論的根拠を理解したいと思いました。

受け入れられた答えは理論的根拠を正確に理解しました。これは、この機能を設計したチームからの確認です(強調は私のものです):

2つの新しいタイプがフレームワークの基礎を形成します。A CancellationTokenは「キャンセルの潜在的な要求」を表す構造体です。この構造体はパラメーターとしてメソッド呼び出しに渡され、メソッドはそれをポーリングするか、キャンセルが要求されたときに呼び出されるコールバックを登録できます。A CancellationTokenSourceは、キャンセル要求を開始するためのメカニズムを提供するクラスでありToken 、関連付けられたトークンを取得するためのプロパティを持っています。これらの2つのクラスを1つに統合するのは自然なことですが、この設計により、2つの主要な操作(キャンセル要求の開始とキャンセルの監視と応答)を明確に分離できます。特に、のみを受け取るメソッドCancellationTokenはキャンセルリクエストを監視できますが、キャンセルリクエストを開始することはできません。

リンク:.NET 4キャンセルフレームワーク

私の意見ではCancellationToken、状態を観察するだけで変更はできないという事実は非常に重要です。あなたはキャンディーのようにトークンを配ることができ、あなた以外の誰かがそれをキャンセルすることを心配する必要はありません。それは敵対的なサードパーティのコードからあなたを守ります。はい、可能性はわずかですが、個人的にはその保証が好きです。

また、APIがよりクリーンになり、偶発的な間違いを回避し、より良いコンポーネント設計を促進できると感じています。

これらのクラスの両方のパブリックAPIを見てみましょう。

CancellationToken API

CancellationTokenSource API

それらを組み合わせると、LongRunningFunctionを作成するときに、使用してはならない「キャンセル」の複数のオーバーロードのようなメソッドが表示されます。個人的には、Disposeメソッドも見たくありません。

現在のクラス設計は「成功の秘訣」の哲学に従っていると思います。それは、Taskキャンセルを処理し、複雑なワークフローを作成するためにさまざまな方法でそれらを一緒にインスツルメントできるより良いコンポーネントを作成するように開発者を導きます。

質問させてください、トークンの目的は何ですか?それは私には意味がありませんでした。その後、Managed ThreadsのCancellationを読み、すべてが非常に明確になりました。

TPLのキャンセルフレームワークデザインは、絶対に最高です。


2
CancellationTokenSource関連付けられたトークンでキャンセルリクエストを実際にどのように開始できるのか不思議に思っている場合(トークンはそれ自体では実行できません):CancellationTokenにはこの内部コンストラクターが internal CancellationToken(CancellationTokenSource source) { this.m_source = source; }あります。public bool IsCancellationRequested { get { return this.m_source != null && this.m_source.IsCancellationRequested; } }また、このプロパティ:CancellationTokenSourceは内部コンストラクターを使用するため、トークンはソース(m_source)
chviLadislav 2017年

65

これらは、技術的な理由ではなく、意味上の理由で分離されています。CancellationTokenILSpy の下での実装を見ると、 それは単なるラッパーであることがわかりますCancellationTokenSource(したがって、パフォーマンスの点で、参照を渡すことと違いはありません)。

これらは機能の分離を提供し、物事をより予測可能にします。メソッドaを渡してCancellationTokenも、それをキャンセルできるのはまだ自分だけであることがわかります。もちろん、メソッドはをスローできますTaskCancelledExceptionが、CancellationTokenそれ自体(および同じトークンを参照する他のメソッド)は安全なままです。


ありがとう!私のまばたきの反応は、意味的には、IReadOnlyListを提供することと同じくらい、疑わしいアプローチだということです。それは非常にもっともらしいように聞こえるので、あなたの答えを受け入れる。
Andrey Tarantsov 2013年

1
さらに考えてみると、私はその妥当性に疑問を感じています。では、CancellationTokenSourceが実装するICancellationTokenインターフェイスを提供するほうが理にかなっているのではないでしょうか。私は、.NETチームから誰かが中にチャイムほしい。
アンドレイTarantsov

それでも、巧妙なプログラマがにキャストする可能性がありCancellationTokenSourceます。あなたは「それをしないでください」とだけ言うことができると思いますが、人々(私を含む!)はとにかくこれらのことを行って、いくつかの隠された機能を取得します。それが少なくとも私の現在の理論です。
Cory Nelson

1
セキュリティ関連でない限り、ほとんどの場合、それはAPIの設計方法ではありません。「将来的にパフォーマンスを最適化するためにオプションを開いたままにする」など、他のいくつかの説明に賭けたいと思います。しかし、それでも、あなたのものは今のところ最高のものです。
Andrey Tarantsov 2013年

ねえ、私はあなたが実装に関与する人に受け入れられた答えを再割り当てすることを許してくれることを望みます。あなたは両方とも同じことを言っていますが、SOは決定的な答えがトップであることから利益を得ると思います。
Andrey Tarantsov 2015年

10

これCancellationTokenは構造体なので、メソッドに渡されるため、多くのコピーが存在する可能性があります。

CancellationTokenSourceセットの呼び出しトークンのすべてのコピーの状態Cancelソースに。このMSDNページを参照してください

設計の理由は、懸念の分離と構造体の速度の問題だけかもしれません。


1
ありがとう。別の答えは、技術的には(ILで)、CancellationTokenSourceはトークンの状態を設定しないことを示していることに注意してください。代わりに、トークンはCancellationTokenSourceへの実際の参照をラップし、それにアクセスしてキャンセルを確認します。どちらかといえば、ここでしか速度を失うことができません。
Andrey Tarantsov 2013年

+1。わかりません。値タイプの場合、各メソッドには独自の値(個別の値)があります。では、キャンセルが呼び出されたかどうかをメソッドはどのように知るのでしょうか?TokenSourceへの参照はありません。メソッドが見ることができるのは、「5」のようなローカル値タイプだけです。説明できますか ?
Royi Namir 2013

1
@RoyiNamir:各CancellationTokenには、タイプCancellationTokenSourceのプライベート参照「m_source」があります
Andreyul

2

CancellationTokenSource何らかの理由で、キャンセルを出す「事」です。それCancellationTokenが発行したすべてのキャンセルを「ディスパッチ」する方法が必要です。たとえば、ASP.NETでは、要求が中止されたときに操作をキャンセルできます。各要求には、CancellationTokenSource発行したすべてのトークンにキャンセルを転送するがあります。

これは、BTWの単体テストに最適です。独自のキャンセルトークンソースを作成し、トークンを取得Cancelして、ソースを呼び出し、キャンセルを処理する必要があるコードにトークンを渡します。


おかげで-しかし、(両方とも責任ある非常に関連する)は、同じクラスに与えることができます。それらが分離している理由を実際には説明しません。
Andrey Tarantsov 2013年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.