次のようにタスクを開始するプログラムがあります。
token = this.tokenSource.Token;
var t = Task.Factory.StartNew(() =>
{
while(!token.IsCancellationRequested)
{
// do the work...
}
}, token);
このタスクの私の目標は、ユーザーがプログラムを停止するまで、いくつかの作業を実行することです。ユーザーがプログラムを停止するには、次のようにこのタスクの実行を停止する必要があります。
this.tokenSource.Cancel();
スレッドはキャンセルされたトークンソースを監視するため、停止します。私は不思議に思っています...それは次のようなことと何が違うのですか?
private bool isCancelled = false;
public void Start()
{
var t = Task.Factory.StartNew(() =>
{
while (!this.isCancelled)
{
// do the work...
}
});
}
public void Stop()
{
this.isCancelled = true;
}
私は通常、最初の方法でプログラムを実行します(まだ失敗していません)が、何らかの理由で2番目に自分自身を推測しています。これらは両方とも悪い習慣ですか?
2番目は、複数のスレッド間で共有しているメモリへのアクセスを適切に同期させないため、必ずしも意図したとおりに機能するわけではありません。たとえば、作業を行っているスレッドプールスレッドは、その変数に書き込んでいないことを確認できます(別のスレッドがそうである可能性がありますが、明示的なものがない場合、他のスレッドが何をしているかを知る必要もありません)同期が行われているため)、必要に応じてメモリからフェッチするのではなく、ブール値をキャッシュすることができます。その値を読み取る頻度を考えると、ブール値を使用する可能性が高くなります。これにより、おそらくかなり長い間、他のスレッドからの変更に気付かなくなります。そしてもちろん、この問題が発生しないという事実every時間は、それがdoesのときにそれを診断することをますます難しくします。
CancellationToken
は必要なすべての同期を処理します必要がないように、そしてそれはブートするために可能な限り効率的に実行します。
また、設計の観点からもしばしばメリットがあります。操作のキャンセルを担当する担当者と、そのキャンセルを操作する担当者を区別できます。その結果、ワーカーとその呼び出し元/消費者の間の結合がより緩くなります。その労働者はanyoneのために仕事をすることができます。キャンセルを特定の場所の特定の変数に結び付けるものではありません。操作のキャンセルを担当するエンティティが、作業を担当するエンティティから削除されたいくつかのレイヤーである場合、これはなおさら関連性があります。ブール値だけでそれを管理しようとするよりも、キャンセルが発生したときに確認するためにそれをチェックする何かにcancelationtokenを渡す方がはるかに簡単です。それは、それらの間の抽象化のすべての層にもかかわらず、それらのエンティティの1つが他方について明示的に知る必要があることを意味するか、またはCancellationToken
が行うことに類似した方法でそのブール値をカプセル化する必要があります。
さらに、キャンセルトークンの概念を抽象化することで、キャンセルトークンに作用する/キャンセルトークンを使用するより高いレベルの操作を作成できます。これにより、一定期間後にキャンセルされるキャンセルトークンを作成したり、すべてのトークンセットがキャンセルされた場合、または一連のトークンのいずれかがキャンセルされた場合にのみキャンセルされるキャンセルトークンを作成したりできます。 。