我有以下代码,输出如下。 我期待第二个任务被取消,因为它还会在取消令牌上注册一个回调。 但是取消仅在完成原始取消的第一个任务上发生。 取消不应该传播到所有令牌实例吗? Microsoft article on Cancellation Tokens不能很好地解释这一点。
关于为什么会发生这种情况的任何指示?
代码:
const x_axis = d3.axisBottom()
.scale(x);
输出:
const x_axis = d3.axisBottom()
.scale(x)
.ticks(5);
答案 0 :(得分:2)
失败的不仅仅是第二项任务。令牌的两个注册均有效,并且ThrowIfCancellationRequested
均有效,但是由于它们在不同的线程中运行,因此无法处理。
这在后台发生(两次):
mscorlib.dll中发生了'System.OperationCanceledException'类型的异常,但未在用户代码中处理
您应该做的是在函数中调用cts.Token.ThrowIfCancellationRequested();
而不是注册事件。
请参见https://docs.microsoft.com/en-us/dotnet/standard/threading/cancellation-in-managed-threads
上的示例现在,您将结合两种取消方式:注册到令牌取消事件(Token.Register
)和如果令牌被取消则抛出(Token.ThrowIfCancellationRequested
)。
要么订阅cancel事件并执行自己的cancel / cleanup逻辑,要么检查功能码是否应该取消操作。
示例如下:
private static async Task CreateTask2(CancellationToken token)
{
try
{
// Pass on the token when calling other functions.
await Task.Delay(8000, token);
// And manually check during long operations.
for (int i = 0; i < 10000; i++)
{
// Do we need to cancel?
token.ThrowIfCancellationRequested();
// Simulating work.
Thread.SpinWait(5000);
}
Console.WriteLine("This is task two");
}
catch (Exception e)
{
Console.WriteLine("Task 2 was cancelled by Task 1");
Console.WriteLine(e);
}
}
答案 1 :(得分:1)
第一件事是,当您调用CancellationToken.Register
时,通常所做的只是存储委托以便以后调用。
调用CancellationTokenSource.Cancel
的线程/逻辑流运行所有先前注册的委托,无论这些代理从何处注册。这意味着在这些异常中抛出的任何异常通常都与称为 Register 的方法没有任何关系。
旁注1:我在上面通常说 ,因为在某些情况下,对Register
的调用将立即使委托运行。我认为这就是msdn文档特别令人困惑的原因。具体来说:如果令牌已被取消,则Register
将立即运行委托,而不是存储它以便以后运行。在CancellationTokenSource.InternalRegister
下发生的情况。
完成图片的第二件事是,CancellationToken.ThrowIfCancellationRequested
所做的所有事情都是在从何处运行时引发异常。通常无论从何处调用CancellationTokenSource.Cancel
。请注意,通常所有注册的委托都已运行,即使其中一些抛出异常。
旁注2:抛出ThreadAbortException
会改变Cancel
方法中的预期逻辑,因为无法捕获该特殊异常。面对这种情况,cancel停止运行任何其他委托。即使捕获异常,调用代码也会发生同样的情况。
最后要注意的是,CancellationToken的存在不会影响方法的逻辑流程。除非有代码明确地退出该方法(例如,通过引发异常),否则该方法中的所有行都将运行。如果将取消令牌传递给Task.Delay调用,并且在时间流逝之前从其他地方取消,则会发生这种情况。如果您在方法中的特定行之后向CancellationToken.ThrowIfCancellationRequested
进行调用,也会发生这种情况。
答案 2 :(得分:0)
Register
注册代表只是一种通知令牌何时进入取消状态的方法,仅此而已。为了执行取消操作,您需要对代码中的此通知做出反应,而当您要取消执行时,则最需要它进行到未验证取消令牌的阶段(例如,因为正在执行的方法只是不接受CancellationToken
作为参数),但您仍需要对取消状态进行一些控制。但是在所有情况下,当您处理有权访问CancellationToken
的代码执行时,您都不需要订阅取消通知。
在您的情况下,第一个委托引发异常,并且此异常仅传播到Cancel
调用,这就是为什么任务被取消的原因,但这是不正确的设计,因为您不应该在其中处理CancellationTokenSource
您的任务,不应该在那里取消,所以我说第一次取消仅是巧合。对于第二个任务,委托被调用,但是没有任何触发任务内部的取消,那么为什么要取消它呢?