使用 CancellationToken 取消任务而不在任务中明确检查?
Using a CancellationToken to cancel a task without explicitly checking within the task?
背景:
我有一个 Web 应用程序可以启动长 运行(和无状态)任务:
var task = Task.Run(() => await DoWork(foo))
task.Wait();
因为它们很长 运行,我需要能够从单独的 Web 请求中取消它们。
为此,我想使用 CancellationToken 并在令牌被取消后立即抛出异常。但是,根据我的阅读,任务取消是 cooperative,这意味着任务是 运行 的代码必须明确检查令牌以查看是否已发出取消请求(例如 CancellationToken.ThrowIfCancellation()
)
我想避免到处检查 CancellationToken.ThrowIfCancellation()
,因为这个任务很长并且要经过很多函数。我想我可以完成我想要创建显式 Thread
的任务,但我真的很想避免手动线程管理。也就是说...
问题:
是否有可能在任务被取消时自动在任务中抛出异常,如果没有,是否有任何好的替代方法(模式等)来减少使用 CancellationToken.ThrowIfCancellation()
?
污染代码
我想避免这样的事情:
async Task<Bar> DoWork(Foo foo)
{
CancellationToken.ThrowIfCancellation()
await DoStuff1();
CancellationToken.ThrowIfCancellation()
await DoStuff2();
CancellationToken.ThrowIfCancellation()
await DoStuff3();
...
}
我觉得这个问题与 this one 有很大的不同,因为我明确要求一种方法来最小化检查取消令牌的调用,接受的答案响应 "Every now and then, inside the functions, call token.ThrowIfCancellationRequested()"
如果您实际上只有一堆方法调用,您可以一个接一个地调用,您可以实现一个方法运行器,按顺序运行它们并在中间检查是否取消。
像这样:
public static void WorkUntilFinishedOrCancelled(CancellationToken token, params Action[] work)
{
foreach (var workItem in work)
{
token.ThrowIfCancellationRequested();
workItem();
}
}
你可以这样使用它:
async Task<Bar> DoWork(Foo foo)
{
WorkUntilFinishedOrCancelled([YourCancellationToken], DoStuff1, DoStuff2, DoStuff3, ...);
}
这基本上可以满足您的要求。
Is it possible to automatically throw an exception in the task when it has been canceled, and if not, are there any good alternatives (patterns, etc.) to reduce polluting the code with CancellationToken.ThrowIfCancellation()?
没有,也没有。所有取消都是合作的。取消代码的最佳方式是让代码响应取消请求。这是唯一好的模式。
I think I can accomplish what I want creating an explicit Thread
不是真的。
此时,问题是 "how do I cancel uncancelable code?" 答案取决于您希望系统的稳定性:
- 运行 代码在单独的
Thread
和 Abort
线程中时不再需要。这是最容易实现的,但在应用程序不稳定方面也是最危险的。坦率地说,如果您曾在应用中的任何地方调用 Abort
,除了 heartbeat/smoketest 检查等标准做法之外,您还应该定期重启该应用。
- 运行 代码在单独的
AppDomain
和 Unload
即 AppDomain 时不再需要。这更难实现(您必须使用远程处理),并且在 Core 世界中不是一个选项。事实证明,AppDomain
s 甚至没有像它们应该的那样保护包含应用程序,因此任何使用此技术的应用程序也需要定期重新启动。
- 运行 代码在单独的
Process
和 Kill
中,当它不再需要时处理。这是最复杂的实施方式,因为您还需要实施某种形式的 inter-process 通信。但这是取消不可取消代码的唯一可靠的解决方案。
如果您丢弃不稳定的解决方案 (1) 和 (2),那么唯一剩下的解决方案 (3) 是 吨 的工作 - 远远超过制作代码可取消。
TL;DR:只需按照设计使用的方式使用取消 API。这是最简单有效的解决办法。
如果您对 Thread.Abort
的 implications 没问题(一次性物品未处理,锁定未释放,应用程序状态已损坏),那么您可以通过以下方式实现 non-cooperative 取消中止任务的专用线程。
private static Task<TResult> RunAbortable<TResult>(Func<TResult> function,
CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<TResult>();
var thread = new Thread(() =>
{
try
{
TResult result;
using (cancellationToken.Register(Thread.CurrentThread.Abort))
{
result = function();
}
tcs.SetResult(result);
}
catch (ThreadAbortException)
{
tcs.TrySetCanceled();
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
});
thread.IsBackground = true;
thread.Start();
return tcs.Task;
}
用法示例:
var cts = new CancellationTokenSource();
var task = RunAbortable(() => DoWork(foo), cts.Token);
task.Wait();
背景:
我有一个 Web 应用程序可以启动长 运行(和无状态)任务:
var task = Task.Run(() => await DoWork(foo))
task.Wait();
因为它们很长 运行,我需要能够从单独的 Web 请求中取消它们。
为此,我想使用 CancellationToken 并在令牌被取消后立即抛出异常。但是,根据我的阅读,任务取消是 cooperative,这意味着任务是 运行 的代码必须明确检查令牌以查看是否已发出取消请求(例如 CancellationToken.ThrowIfCancellation()
)
我想避免到处检查 CancellationToken.ThrowIfCancellation()
,因为这个任务很长并且要经过很多函数。我想我可以完成我想要创建显式 Thread
的任务,但我真的很想避免手动线程管理。也就是说...
问题:
是否有可能在任务被取消时自动在任务中抛出异常,如果没有,是否有任何好的替代方法(模式等)来减少使用 CancellationToken.ThrowIfCancellation()
?
我想避免这样的事情:
async Task<Bar> DoWork(Foo foo)
{
CancellationToken.ThrowIfCancellation()
await DoStuff1();
CancellationToken.ThrowIfCancellation()
await DoStuff2();
CancellationToken.ThrowIfCancellation()
await DoStuff3();
...
}
我觉得这个问题与 this one 有很大的不同,因为我明确要求一种方法来最小化检查取消令牌的调用,接受的答案响应 "Every now and then, inside the functions, call token.ThrowIfCancellationRequested()"
如果您实际上只有一堆方法调用,您可以一个接一个地调用,您可以实现一个方法运行器,按顺序运行它们并在中间检查是否取消。
像这样:
public static void WorkUntilFinishedOrCancelled(CancellationToken token, params Action[] work)
{
foreach (var workItem in work)
{
token.ThrowIfCancellationRequested();
workItem();
}
}
你可以这样使用它:
async Task<Bar> DoWork(Foo foo)
{
WorkUntilFinishedOrCancelled([YourCancellationToken], DoStuff1, DoStuff2, DoStuff3, ...);
}
这基本上可以满足您的要求。
Is it possible to automatically throw an exception in the task when it has been canceled, and if not, are there any good alternatives (patterns, etc.) to reduce polluting the code with CancellationToken.ThrowIfCancellation()?
没有,也没有。所有取消都是合作的。取消代码的最佳方式是让代码响应取消请求。这是唯一好的模式。
I think I can accomplish what I want creating an explicit Thread
不是真的。
此时,问题是 "how do I cancel uncancelable code?" 答案取决于您希望系统的稳定性:
- 运行 代码在单独的
Thread
和Abort
线程中时不再需要。这是最容易实现的,但在应用程序不稳定方面也是最危险的。坦率地说,如果您曾在应用中的任何地方调用Abort
,除了 heartbeat/smoketest 检查等标准做法之外,您还应该定期重启该应用。 - 运行 代码在单独的
AppDomain
和Unload
即 AppDomain 时不再需要。这更难实现(您必须使用远程处理),并且在 Core 世界中不是一个选项。事实证明,AppDomain
s 甚至没有像它们应该的那样保护包含应用程序,因此任何使用此技术的应用程序也需要定期重新启动。 - 运行 代码在单独的
Process
和Kill
中,当它不再需要时处理。这是最复杂的实施方式,因为您还需要实施某种形式的 inter-process 通信。但这是取消不可取消代码的唯一可靠的解决方案。
如果您丢弃不稳定的解决方案 (1) 和 (2),那么唯一剩下的解决方案 (3) 是 吨 的工作 - 远远超过制作代码可取消。
TL;DR:只需按照设计使用的方式使用取消 API。这是最简单有效的解决办法。
如果您对 Thread.Abort
的 implications 没问题(一次性物品未处理,锁定未释放,应用程序状态已损坏),那么您可以通过以下方式实现 non-cooperative 取消中止任务的专用线程。
private static Task<TResult> RunAbortable<TResult>(Func<TResult> function,
CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<TResult>();
var thread = new Thread(() =>
{
try
{
TResult result;
using (cancellationToken.Register(Thread.CurrentThread.Abort))
{
result = function();
}
tcs.SetResult(result);
}
catch (ThreadAbortException)
{
tcs.TrySetCanceled();
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
});
thread.IsBackground = true;
thread.Start();
return tcs.Task;
}
用法示例:
var cts = new CancellationTokenSource();
var task = RunAbortable(() => DoWork(foo), cts.Token);
task.Wait();