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

How to cancel an existing task and run a new task when it completes?

我有一个很长的 运行 计算,它依赖于输入值。如果在计算运行时更改了输入值,应取消当前计算并在前一个计算完成后开始新的计算。

基本思路如下:

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 方法,以便用较新的 async/await 技术替换旧学校 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;
    }
}

此实现不是 thread-safe。它要求 UpdateCalculation 总是从 UI 线程调用。

documentation 发出强烈警告,说明必须处理 CancellationTokenSource:

Always call Dispose before you release your last reference to the CancellationTokenSource. Otherwise, the resources it is using will not be freed until the garbage collector calls the CancellationTokenSource object's Finalize method.

相关问题:When to dispose CancellationTokenSource?