链接取消令牌



我使用一个传递的取消令牌,这样我的服务就可以干净地关闭。该服务具有不断尝试连接到其他服务的逻辑,因此令牌是打破这些在单独线程中运行的重试循环的好方法。我的问题是,我需要调用一个具有内部重试逻辑的服务,但如果重试失败,则在设置的时间段后返回。我想创建一个有超时的新取消令牌,这将为我完成这项工作。问题是我的新令牌没有链接到"主"令牌,所以当主令牌被取消时,我的新代币将仍然有效,直到它超时或建立连接并返回。我想做的是将两个代币链接在一起,这样当主代币被取消时,我的新代币也会被取消。我尝试使用CancellationTokenSource.CreateLinkedTokenSource方法,但当我的新令牌超时时,它也取消了主令牌。有没有一种方法可以用令牌做我需要做的事情,或者它需要更改重试逻辑(可能无法轻松做到这一点)

以下是我想做的:

主令牌-传递各种功能,以便服务可以干净地关闭。临时令牌–传递给单个功能,并在一分钟后设置为超时

如果主令牌被取消,临时令牌也必须被取消。

当临时令牌到期时,不得取消主令牌。

您想要使用CancellationTokenSource.CreateLinkedTokenSource。它可以让你有一个";"父";CCD_ 3;"孩子";CancellationTokenSource

这里有一个简单的例子:

var parentCts = new CancellationTokenSource();
var childCts = CancellationTokenSource.CreateLinkedTokenSource(parentCts.Token);

childCts.CancelAfter(1000);
Console.WriteLine("Cancel child CTS");
Thread.Sleep(2000);
Console.WriteLine("Child CTS: {0}", childCts.IsCancellationRequested);
Console.WriteLine("Parent CTS: {0}", parentCts.IsCancellationRequested);
Console.WriteLine();

parentCts.Cancel();
Console.WriteLine("Cancel parent CTS");
Console.WriteLine("Child CTS: {0}", childCts.IsCancellationRequested);
Console.WriteLine("Parent CTS: {0}", parentCts.IsCancellationRequested);

预期输出:

取消子CTS
子CTS:True
父CTS:False

取消父CTS
子CTS:True
父CTS:Ttrue

如果您只有一个CancellationToken,而不是CancellationTokenSource,那么仍然可以创建一个链接的取消令牌。您只需使用Register方法来触发(伪)子级的取消:

var child = new CancellationTokenSource();
token.Register(child.Cancel);

你可以用CancellationTokenSource做任何你通常会做的事情。例如,您可以在一段时间后取消它,甚至覆盖您以前的令牌。

child.CancelAfter(cancelTime);
token = child.Token;

iArnon已经回答了,您可以使用CancellationTokenSource.CreateLinkedTokenSource()来完成此操作。我想展示一种模式,当你想区分取消整体任务和取消子任务而不取消整体任务时,如何使用这种令牌。

async Task MyAsyncTask(
CancellationToken ct)
{
// Keep retrying until the master process is cancelled.
while (true)
{
// Ensure we cancel ourselves if the parent is cancelled.
ct.ThrowIfCancellationRequested();
using var childCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
// Set a timeout because sometimes stuff gets stuck.
childCts.CancelAfter(TimeSpan.FromSeconds(32));
try
{
await DoSomethingAsync(childCts.Token);
}
// If our attempt timed out, catch so that our retry loop continues.
// Note: because the token is linked, the parent token may have been
// cancelled. We check this at the beginning of the while loop.
catch (OperationCancelledException) when (childCts.IsCancellationRequested)
{
}
}
}

当临时令牌到期时,不得取消主令牌。

请注意,MyAsyncTask()的签名接受CancellationToken而不是CancellationTokenSource。由于该方法只能访问CancellationToken上的成员,因此不能意外取消主/父令牌。我建议您组织代码,使主任务的CancellationTokenSource对尽可能少的代码可见。在大多数情况下,这可以通过将CancellationTokenSource.Token传递给方法来实现,而不是共享对CancellationTokenSource的引用。

我还没有调查过,但可能有一种方法,比如反射,可以在不访问CancellationTokenSource的情况下强制取消CancellationToken。希望这是不可能的,但如果可能的话,这将被认为是糟糕的做法,一般来说不需要担心

一些答案提到了从父令牌创建链接的令牌源。如果您从其他地方获得子令牌,则此模式将崩溃。相反,您可能希望从主令牌和传递给方法的令牌中创建一个链接的令牌源。

根据Microsoft文档:https://learn.microsoft.com/en-us/dotnet/standard/threading/how-to-listen-for-multiple-cancellation-requests

public void DoWork(CancellationToken externalToken)
{
// Create a new token that combines the internal and external tokens.
this.internalToken = internalTokenSource.Token;
this.externalToken = externalToken;
using (CancellationTokenSource linkedCts =
CancellationTokenSource.CreateLinkedTokenSource(internalToken, externalToken))
{
try {
DoWorkInternal(linkedCts.Token);
}
catch (OperationCanceledException) when (linkedCts.Token.IsCancellationRequested){
if (internalToken.IsCancellationRequested) {
Console.WriteLine("Operation timed out.");
}
else if (externalToken.IsCancellationRequested) {
Console.WriteLine("Cancelling per user request.");
externalToken.ThrowIfCancellationRequested();
}
}
catch (Exception ex)
{
//standard error logging here
}
}
}

通常,在向方法传递令牌时,取消令牌是您可以访问的全部。要使用其他答案的方法,您可能需要重新管道传输所有其他方法以绕过令牌源。这个方法只允许您使用令牌。

最新更新