任务取消,什么时候?
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
,那就直接通过吧。
为了实现 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
,那就直接通过吧。