3

呼び出し元のスレッドをブロックするがキャンセルをサポートしないメソッドの (ばかげた) 例を次に示します。

Public Sub WorkUntil5()
    Threading.SpinWait.SpinUntil(Function() Now.Hour >= 17)
End Sub

最悪の場合、このメソッドを呼び出してから戻るまでに 17 時間かかります。このメソッドのソース コードにアクセスできないふりをします。CancellationToken を受け取るメソッドで呼び出しをラップするにはどうすればよいですか?

目標は、WorkUntil5()キャンセルが要求されるまで実行することです。その時点で、コールは可能な手段を使用して終了する必要があります。

これが私が自分自身を思いつくことができる最良の方法です。タスクを使用しますが、それでも呼び出し元のスレッドをブロックします。それについて何かが正しく感じられません。mres.Set()最初の呼び出しが返されたら、より良い呼び出し方法があるはずだと考えています。

Public Sub WorkUntilYouGetBored(cancellationToken As Threading.CancellationToken)
    Dim mres As New Threading.ManualResetEventSlim

    Using cancellationToken.Register(Sub() mres.Set())
        Dim t = Task.Factory.StartNew(Sub() WorkUntil5())
        t.ContinueWith(Sub() mres.Set())

        mres.Wait()
    End Using

    If cancellationToken.IsCancellationRequested Then
        Console.WriteLine("You went home early.")
    Else
        Console.WriteLine("It's time to go home.")
    End If
End Sub
4

1 に答える 1

3

質問を言い換えましょう。キャンセルしたいWorkUntil5というキャンセル不可能な操作があります。どうやってそれができる?

Stephen Toubは、このシナリオについて「キャンセルできない非同期操作をキャンセルするにはどうすればよいですか?」で説明しています。

唯一の実際の解決策は、キャンセルできるようにWorkUntil5を修正することです。それが不可能な場合(なぜ?)、「キャンセル」とはどういう意味かを決める必要がありますか?

あなたは__したいですか:

  1. 本当に長い操作をキャンセルしますか、それともやりますか
  2. それを待つのをやめますか?

長時間実行されるメソッドで何が未完成のままであるかを知る方法がないため、両方の操作は信頼できません。TPLの設計ではなく、長期的な方法の設計に問題があります。

この方法ではキャンセルする方法がないため、操作#1は実際には不可能です。

操作#2は、TasksとTaskCompletionSourceを使用して処理できますが、長い操作でゴミが残ったのか、例外で終了したのかを知る方法がないため、信頼性は低くなります。

Stephen Toubはこれを行う方法を示し、それを行うための拡張メソッドも提供します。

public static async Task<T> WithCancellation<T>( 
this Task<T> task, CancellationToken cancellationToken) 
{ 
    var tcs = new TaskCompletionSource<bool>(); 
    using(cancellationToken.Register( 
            s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs)) 
        if (task != await Task.WhenAny(task, tcs.Task)) 
            throw new OperationCanceledException(cancellationToken); 
    return await task; 
}
于 2012-12-10T09:40:04.697 に答える