对于API的实现我正在让我希望用户能够取消他们的任务(任务)。我想我需要一个CancellationTokenSource
所以我可以从前者创建一个CancellationToken
。现在我正处理取消请求(IsCancellationRequested
/ ThrowIfCancellationRequested
),我有点困惑。什么是检查/做这个的适当时间?
例如(虚构):
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;
}
取消任务的最佳做法是什么?
答案 0 :(得分:4)
检查/执行此操作的适当时间是什么?
对于CPU绑定方法,定期调用CancellationToken.ThrowIfCancellationRequested
是合适的。例如,如果你有一个严格的处理循环,你可能想要每隔几百次迭代调用它。
但是,听起来你的情况都是I / O绑定的(自然是异步的)。在这种情况下,最好只传递令牌。更高级别的异步方法只能传递令牌。在最低级别,如果API不直接支持CancellationToken
,那么通常最好使用CancellationToken.Register
来实现异步操作的真正取消。当然,如果最低级别的API直接支持CancellationToken
,那么只需通过它。
答案 1 :(得分:3)
取消通常在等待需要时间的操作时完成。例如,等待IO操作或进行基于CPU的计算。
在你的例子中,确实没有必要在#1,#2和#6取消,因为它们只是毫不拖延地完成。
如果列表很大或者计算需要时间,那么在#4中检查取消是有意义的。
最后#5是赢家,因为IO不快。但是,_fileSystem.Save
不会TaskCancelledException
投掷?在那种情况下,它已经被照顾。
请注意,通过取消令牌,您还可以说事情可以恢复。如果没有,请在文档中清楚地表明取消将中止那些尚未保存的计算。
答案 2 :(得分:0)
没有什么叫做#34; Best-Practice&#34;。在不同情况下,它的使用可能会有所不同。
基本上ThrowIfCancellationRequested()
用于防止在提出取消时运行任何额外的代码行。可以将其视为以下类型检查:
if (token.IsCancellationRequested)
throw new OperationCanceledException(token); // break the processing immediately
现在由您来决定在请求取消时应该停止执行哪个代码段。正如@Jgauffin建议的LongRunning代码部分将是进行此类检查的好地方。此外,如果您有多个区域,例如在给定的样本2 for循环中,您可以在两者中使用,以避免在被要求取消后进一步处理。