完成时如何取消现有任务并运行新任务?

问题描述

我有一个长期的计算,取决于输入值。如果在运行计算时更改了输入值,则应取消当前计算,并在上一个计算完成后重新开始计算。

基本思想如下:

Task _latestTask = Task.CompletedTask;
CancellationTokenSource _cancellationTokenSource;

int Value
{
    get => _value;
    set
    {
        _value = value;
        UpdateCalculation();
    }
}

void UpdateCalculation()
{
    _cancellationTokenSource?.Cancel();
    _cancellationTokenSource = new CancellationTokenSource();
    var cancellationToken = _cancellationTokenSource.Token;
    var newTask = new Task(() => DoCalculation(Value,cancellationToken));
    _latestTask.ContinueWith(antecedent => newTask.Start(),cancellationToken);
    _latestTask = newTask;
}

但是,我发现根据设置Value的频率,可能会在开始新任务之前取消继续任务。整个任务链停止了。

我应该如何组织事情,以使更改后的值导致当前的计算被放弃,并且一旦我知道上一个任务已经完成,便开始新的计算?

解决方法

您可以在此处重构UpdateCalculation方法,以用更新的异步/等待技术替换旧的ContinueWith,并确保最终CancellationTokenSource被采用处置。

async void UpdateCalculation()
{
    _cancellationTokenSource?.Cancel();
    using (var cts = new CancellationTokenSource())
    {
        _cancellationTokenSource = cts;
        var previousTask = _latestTask;
        var newTask = new Task(() => DoCalculation(Value,cts.Token),cts.Token);
        _latestTask = newTask;
        // Prevent an exception from any task to crash the application
        // It is possible that the newTask.Start() will throw too
        try { await previousTask } catch { }
        try { newTask.Start(); await newTask; } catch { }
        // Ensure that the CTS will not be canceled after is has been disposed
        if (_cancellationTokenSource == cts) _cancellationTokenSource = null;
    }
}

此实现不是线程安全的。它要求始终从UI线程调用UpdateCalculation

documentation发出强烈警告,指出必须处置CancellationTokenSource

在释放对Dispose的最后引用之前,请始终致电CancellationTokenSource。否则,除非垃圾回收器调用CancellationTokenSource对象的Finalize方法,否则将不会释放正在使用的资源。

相关问题:When to dispose CancellationTokenSource?