任务取消,什么时候?

Task cancellation, when?

为了实现 API 我想让用户能够取消他们的任务 (Task)。我发现我需要一个 CancellationTokenSource,这样我就可以从前者创建一个 CancellationToken。现在我正在处理取消请求 (IsCancellationRequested/ThrowIfCancellationRequested),我有点困惑。 check/do 什么时候合适?

例如(虚构):

async Task<int> DoStuff(int number, CancellationToken token) {
    // 1. Here, callee-site?
    token.ThrowIfCancellationRequested();

    var resultTask1 = _database.GetSomeDataFromDatabase(token); // 2. Inside this method?
    var resultTask2 = _service.SomeRestCall(token); // 2. Inside this?

    // 3. Here, before the tasks return?
    token.ThrowIfCancellationRequested();
    var result1 = await resultTask1;
    var result2 = await resultTask2;

    // 4. In memory processing, here?
    foreach(var item in result1)
    {
        // ...
    }

    foreach(var item in result2)
    {
        // 1. Here?
        token.ThrowIfCancellationRequested();
        await _fileSystem.Save(fileName, item, token); // 5. Inside this?
    }

    // 6. Here probably doesn't make sense though, the result is already retrieved?
    return 123;
}

取消任务的最佳做法是什么?

取消通常是在等待需要时间的操作时完成的。例如等待 IO 操作或进行基于 CPU 的计算。

在您的示例中,实际上没有必要在#1、#2 和#6 处取消,因为它们会毫不拖延地通过。

但是,如果列表很大或者计算需要时间,那么在#4 中检查取消是有意义的。

最终 #5 获胜,因为 IO 速度不快。但是,_fileSystem.Save 不会抛出 TaskCancelledException 吗?那样的话已经处理好了。

请注意,通过使用取消令牌,您也表示事情是可恢复的。如果没有,请在文档中crystal明确取消只会中止那些尚未保存的计算。

没有什么叫"Best-Practice"。不同场景下用法可能不同

基本上 ThrowIfCancellationRequested() 用于防止 运行 在要求取消时出现任何额外的代码行。将其视为以下类型检查:

if (token.IsCancellationRequested) 
    throw new OperationCanceledException(token); // break the processing immediately

现在由您决定在请求取消时应停止执行哪个代码段。正如@Jgauffin 所建议的,LongRunning 代码部分是进行此类检查的好地方。另外,如果您的例如有多个区域在给定的示例 2 for 循环中,您可以在两者中使用以避免在要求取消后进一步处理。

What would be the proper time to check/do this?

对于 CPU 绑定的方法,定期调用 CancellationToken.ThrowIfCancellationRequested 是合适的。例如,如果您有一个紧密的处理循环,您可能希望每隔几百次迭代左右调用一次。

不过,听起来你的情况都是I/O-bound(天生异步)。在这种情况下,最好 将令牌向下传递 。更高级别的异步方法可以只传递令牌。在最底层,如果 API 不直接支持 CancellationToken,那么一般最好使用 CancellationToken.Register 来实现真正的取消异步操作。当然,如果最底层的API确实直接支持CancellationToken,那就直接通过吧。