5

次のようなタスクがあります。

var task = Task.Factory.StartNew <object>(LongMethod);
task.ContinueWith(TaskCallback, TaskScheduler.FromCurrentSynchronizationContext());

LongMethodは、実行時間の長いサービスを呼び出しますが、その間はキャンセルトークンを常にポーリングして、キャンセルされているかどうかを確認できません(少なくとも、できるとは思わない)。ただし、コールバックメソッドを「キャンセル」または無視することに関心があります。

TaskCallbackが呼び出されたとき、それが最新のタスクからのものである場合にのみ「結果」に関心があります(LongMethodが呼び出すサービスが順序を保持し、ユーザーがボタンを何度もクリックできると仮定します。ただし、関連するのは最新のものだけです)。

次の方法でコードを変更しました。タスクを作成した後、それをスタックの一番上に追加します。TaskCallbackで、コールバックに渡されたタスクが最新のものであるかどうかを確認します(つまり、スタックの最上位にあるTryPeek)。そうでない場合は、結果を無視します。

private ConcurrentStack<Task> _stack = new ConcurrentStack<Task>();

private void OnClick(object sender, ItemClickEventArgs e)
{
    var task = Task.Factory.StartNew < object >( LongMethod);
    task.ContinueWith(TaskCallback, TaskScheduler.FromCurrentSynchronizationContext());
    _stack.Push(task);
 }


private void TaskCallback(Task<object> task)
{
    Task topOfStack;
    if(_stack.TryPeek(out topOfStack)) //not the most recent
    {
        if (task != topOfStack) return;
    }
    //else update UI
}

これは「ベストプラクティス」のソリューションではないと確信しています。しかし、何ですか?キャンセルトークンの受け渡しと維持も、それほどエレガントではないようです。

4

2 に答える 2

6

私は個人的に、次のアプローチが最もエレガントだと思います。

// Cancellation token for the latest task.
private CancellationTokenSource cancellationTokenSource;

private void OnClick(object sender, ItemClickEventArgs e)
{
    // If a cancellation token already exists (for a previous task),
    // cancel it.
    if (this.cancellationTokenSource != null)
        this.cancellationTokenSource.Cancel();

    // Create a new cancellation token for the new task.
    this.cancellationTokenSource = new CancellationTokenSource();
    CancellationToken cancellationToken = this.cancellationTokenSource.Token;

    // Start the new task.
    var task = Task.Factory.StartNew<object>(LongMethod, cancellationToken);

    // Set the task continuation to execute on UI thread,
    // but only if the associated cancellation token
    // has not been cancelled.
    task.ContinueWith(TaskCallback, 
        cancellationToken, 
        TaskContinuationOptions.NotOnCanceled, 
        TaskScheduler.FromCurrentSynchronizationContext());
}

private void TaskCallback(Task<object> task)
{
    // Just update UI
}
于 2012-06-22T21:31:38.950 に答える
1

長時間実行されているタスクをキャンセルすることはできませんが、ユーザーがキャンセルしたときに、ユーザーの観点からプロセスを即座に中止することを望んでいることを理解しています。

長時間実行するタスクと並行してキャンセルタスクを開始します。続行してTask.WhenAny(longRunningTask, cancelTask)、完了したタスクが長時間実行タスクであるかキャンセルタスクであるかを確認します。次に、何をするかを決定できます(結果を表示するかUIを更新します)。

「キャンセルタスク」をキャンセルすると、継続が即座に発生します。

于 2012-06-22T21:44:02.943 に答える