私がタスクについて読んだことから、次のコードは例外をスローすることなく現在実行中のタスクをキャンセルするはずです。私は、タスクの取り消しの全体のポイントは、スレッドを中断せずに停止するようにタスクを丁寧に「求める」ことであるという印象を受けていました。
次のプログラムからの出力は次のとおりです。
ダンプ例外
[OperationCanceledException]
最後に計算された素数をキャンセルして返します。
キャンセルする際に例外を回避しようとしています。どうすればこれを達成できますか?
void Main()
{
var cancellationToken = new CancellationTokenSource();
var task = new Task<int>(() => {
return CalculatePrime(cancellationToken.Token, 10000);
}, cancellationToken.Token);
try
{
task.Start();
Thread.Sleep(100);
cancellationToken.Cancel();
task.Wait(cancellationToken.Token);
}
catch (Exception e)
{
Console.WriteLine("Dumping exception");
e.Dump();
}
}
int CalculatePrime(CancellationToken cancelToken, object digits)
{
int factor;
int lastPrime = 0;
int c = (int)digits;
for (int num = 2; num < c; num++)
{
bool isprime = true;
factor = 0;
if (cancelToken.IsCancellationRequested)
{
Console.WriteLine ("Cancelling and returning last calculated prime.");
//cancelToken.ThrowIfCancellationRequested();
return lastPrime;
}
// see if num is evenly divisible
for (int i = 2; i <= num/2; i++)
{
if ((num % i) == 0)
{
// num is evenly divisible -- not prime
isprime = false;
factor = i;
}
}
if (isprime)
{
lastPrime = num;
}
}
return lastPrime;
}
この行で明示的に例外をスローしています:
_cancelToken.ThrowIfCancellationRequested();
_
タスクを正常に終了する場合は、その行を削除するだけです。
通常、人々はこれを制御メカニズムとして使用して、余分なコードを実行することなく現在の処理を確実に中止します。また、ThrowIfCancellationRequested()
を呼び出すときにキャンセルをチェックする必要はありません。機能的には次と同等です。
_if (token.IsCancellationRequested)
throw new OperationCanceledException(token);
_
ThrowIfCancellationRequested()
を使用すると、タスクは次のようになります。
_int CalculatePrime(CancellationToken cancelToken, object digits) {
try{
while(true){
cancelToken.ThrowIfCancellationRequested();
//Long operation here...
}
}
finally{
//Do some cleanup
}
}
_
また、トークンがキャンセルされた場合、Task.Wait(CancellationToken)
は例外をスローします。このメソッドを使用するには、_Try...Catch
_ブロックでWait呼び出しをラップする必要があります。
キャンセルする際に例外を回避しようとしています。
あなたはそれをするべきではありません。
OperationCanceledException
を投げることは、「呼び出したメソッドが取り消された」という慣用的な方法であり、TPLで表現されます。それと戦わないでください-ただ期待してください。
これはgoodのことです。これは、同じキャンセルトークンを使用して複数の操作を行ったときに、コードをペッパー化する必要がないことを意味するためです。呼び出したばかりのメソッドが実際に正常に完了したかどうか、またはキャンセルのために返されたかどうかを確認するためのすべてのレベル。あなたはcouldで CancellationToken.IsCancellationRequested
を使用しますが、長い目で見ればコードのエレガントさは大幅に低下します。
あなたの例には2つのコードがあり、例外をスローしていることに注意してください。
cancelToken.ThrowIfCancellationRequested()
そして、タスクが完了するまで待つ場所:
task.Wait(cancellationToken.Token);
キャンセルトークンをtask.Wait
呼び出しに渡したいとは思わない、正直に言うと...otherキャンセルするコード待機中あなたがそのトークンをキャンセルしたことがわかっているので、それは無意味です-タスクが実際にキャンセルに気づいたかどうかにかかわらず、例外をスローするのはboundですありません。オプション:
上記の回答のいくつかは、ThrowIfCancellationRequested()
がオプションであるかのように読みます。この場合、notになります。これは、結果の最終素数を取得できないためです。 idiomatic way that "the method you called was cancelled"
は、キャンセルとは(中間の)結果を破棄することを意味する場合に定義されます。キャンセルの定義が「計算を停止して最後の中間結果を返す」である場合は、すでにその方法を終了しています。
特にランタイムの面で利点を議論することも非常に誤解を招く可能性があります:実装されたアルゴリズムは実行時に下手です。高度に最適化されたキャンセルでも効果はありません。
最も簡単な最適化は、このループを展開し、不要なサイクルをスキップすることです。
for(i=2; i <= num/2; i++) {
if((num % i) == 0) {
// num is evenly divisible -- not prime
isprime = false;
factor = i;
}
}
あなたはできる
これは、次のように置き換えるだけで、内側ループのサイクルの保証最小値75%(概算:90%)を節約することになります
if ((num % 2) == 0) {
isprime = false;
factor = 2;
} else {
for(i=3; i <= (int)Math.sqrt(num); i+=2) {
if((num % i) == 0) {
// num is evenly divisible -- not prime
isprime = false;
factor = i;
break;
}
}
}
はるかに高速なアルゴリズムがあります(トピックから十分離れているので説明しません)が、この最適化は非常に簡単であり、それでも私のポイントを証明しています:アルゴリズムがthis最適にはほど遠い。
ThrowIfCancellationRequested
ではなくIsCancellationRequested
を使用する利点に関する別の注意:ContinueWith
を継続オプションTaskContinuationOptions.OnlyOnCanceled
とともに使用する必要がある場合、IsCancellationRequested
は条件付きContinueWith
発砲。 ThrowIfCancellationRequested
、ただし、willタスクのキャンセル条件を設定し、ContinueWith
を起動します。
注:これは、タスクが既に実行されている場合にのみ当てはまり、タスクの開始時には当てはまりません。これが、開始とキャンセルの間にThread.Sleep()
を追加した理由です。
CancellationTokenSource cts = new CancellationTokenSource();
Task task1 = new Task(() => {
while(true){
if(cts.Token.IsCancellationRequested)
break;
}
}, cts.Token);
task1.ContinueWith((ant) => {
// Perform task1 post-cancellation logic.
// This will NOT fire when calling cst.Cancel().
}
Task task2 = new Task(() => {
while(true){
cts.Token.ThrowIfCancellationRequested();
}
}, cts.Token);
task2.ContinueWith((ant) => {
// Perform task2 post-cancellation logic.
// This will fire when calling cst.Cancel().
}
task1.Start();
task2.Start();
Thread.Sleep(3000);
cts.Cancel();