我使用传递的取消令牌,以便可以干净地关闭我的服务。该服务具有不断尝试连接到其他服务的逻辑,因此该令牌是打破在单独线程中运行的这些重试循环的好方法。我的问题是我需要调用一个具有内部重试逻辑的服务,但是如果重试失败则在一段时间后返回。我想创建一个带有超时的新取消令牌,这将为我做到这一点。这个问题是我的新令牌没有链接到“主”令牌,因此当取消主令牌时,我的新令牌仍然有效,直到它超时或建立连接并返回。我想要做的是将两个令牌链接在一起,这样当主要的一个被取消时,我的新一个也会取消。我尝试使用CancellationTokenSource.CreateLinkedTokenSource
方法但是当我的新令牌超时时,它也取消了主令牌。有没有办法做我需要做的令牌,或者它需要更改重试逻辑(可能不会轻易做到这一点)
这是我想要做的:
主令牌 - 传递各种功能,以便服务可以干净地关闭。 临时令牌 - 传递给单个函数并在一分钟后设置为超时
如果取消主令牌,则还必须取消临时令牌。
当临时令牌过期时,它不得取消主令牌。
答案 0 :(得分:45)
您想使用CancellationTokenSource.CreateLinkedTokenSource
。它允许拥有“父母”和“孩子”CancellationTokenSource
es。这是一个简单的例子:
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:真实 父CTS:错误取消父母CTS
儿童CTS:真实 父CTS:是
答案 1 :(得分:1)
作为i3arnon already answered,您可以使用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();
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
的引用来完成此操作。
我没有调查过,但可能有一种方法可以反映强制取消CancellationToken
而无法访问其CancellationTokenSource
。希望这是不可能的,但如果可能的话,那将被认为是不好的做法,而且通常不用担心。
答案 2 :(得分:1)
如果您所拥有的只是一个CancellationToken
,而不是CancellationTokenSource
,那么仍然可以创建链接的取消令牌。您只需使用Register
方法来取消触发(伪)子项的取消:
var child = new CancellationTokenSource();
token.Register(child.Cancel);
您可以执行CancellationTokenSource
通常执行的任何操作。例如,您可以在一段时间后取消它,甚至覆盖以前的令牌。
child.CancelAfter(cancelTime);
token = child.Token;
答案 3 :(得分:0)
有几个答案提到了从父令牌创建链接令牌源。如果您从其他地方传递了子令牌,则此模式将失效。相反,您可能想同时使用主令牌和传递给方法的令牌来创建链接令牌源。
摘自Microsoft文档:https://docs.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) {
if (internalToken.IsCancellationRequested) {
Console.WriteLine("Operation timed out.");
}
else if (externalToken.IsCancellationRequested) {
Console.WriteLine("Cancelling per user request.");
externalToken.ThrowIfCancellationRequested();
}
}
}
}
通常将令牌传递给方法,取消令牌就是您可以访问的全部。要使用其他答案的方法,您可能必须重新使用所有其他方法来传递令牌源。这种方法使您可以只使用令牌。