链接取消令牌
Linking Cancellation Tokens
我使用传递的取消令牌,以便可以干净地关闭我的服务。该服务具有不断尝试连接到其他服务的逻辑,因此令牌是在单独的线程中打破这些重试循环 运行 的好方法。我的问题是我需要调用具有内部重试逻辑的服务,但如果重试失败,则在设定的时间段后调用 return。我想创建一个带有超时的新取消令牌,它将为我执行此操作。问题是我的新令牌没有 linked 到“主”令牌,所以当主令牌被取消时,我的新令牌仍然有效,直到它超时或建立连接并且它 returns。我想做的是 link 将两个令牌放在一起,这样当主令牌被取消时,我的新令牌也会被取消。我尝试使用 CancellationTokenSource.CreateLinkedTokenSource
方法,但是当我的新令牌超时时,它也取消了主令牌。有没有办法用令牌做我需要做的事情,或者它是否需要更改重试逻辑(可能不会轻易做到这一点)
这是我想要做的:
Master Token – 传递各种功能,以便服务可以干净地关闭。
临时令牌 - 传递给单个函数并设置为一分钟后超时
如果Master Token被取消,Temporary Token也必须被取消。
当临时令牌过期时,它不得取消主令牌。
您想使用 CancellationTokenSource.CreateLinkedTokenSource
。它允许有一个 "parent" 和一个 "child" 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);
预期输出:
Cancel child CTS
Child CTS: True
Parent CTS: False
Cancel parent CTS
Child CTS: True
Parent CTS: True
作为,你可以用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)
{
}
}
}
When the Temporary Token expires it must NOT cancel the Master Token.
请注意 MyAsyncTask()
的签名接受 CancellationToken
而不是 CancellationTokenSource
。由于该方法只能访问 CancellationToken
上的成员,因此它不会意外取消 master/parent 标记。我建议您以这样一种方式组织代码,即主任务的 CancellationTokenSource
对尽可能少的代码可见。在大多数情况下,这可以通过将 CancellationTokenSource.Token
传递给方法而不是共享对 CancellationTokenSource
.
的引用来完成
我没有研究过,但可能有一种方法可以通过反射之类的东西强行取消一个CancellationToken
而不访问它的CancellationTokenSource
。希望这是不可能的,但如果可能的话,它会被认为是不好的做法,通常不必担心。
如果您只有 CancellationToken
,而不是 CancellationTokenSource
,那么仍然可以创建链接的取消令牌。您只需使用 Register
方法来触发(伪)子节点的取消:
var child = new CancellationTokenSource();
token.Register(child.Cancel);
您可以做通常使用 CancellationTokenSource
做的任何事情。例如,您可以在一段时间后取消它,甚至覆盖您之前的令牌。
child.CancelAfter(cancelTime);
token = child.Token;
有几个答案提到从父令牌创建链接令牌源。如果您从其他地方传递了子令牌,则此模式将失效。相反,您可能希望从您的主令牌和传递给您的方法的令牌创建链接令牌源。
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
}
}
}
通常在将令牌传递给方法时,您只能访问取消令牌。要使用其他答案的方法,您可能必须重新管道所有其他方法以传递令牌源。此方法可让您仅使用令牌。
我使用传递的取消令牌,以便可以干净地关闭我的服务。该服务具有不断尝试连接到其他服务的逻辑,因此令牌是在单独的线程中打破这些重试循环 运行 的好方法。我的问题是我需要调用具有内部重试逻辑的服务,但如果重试失败,则在设定的时间段后调用 return。我想创建一个带有超时的新取消令牌,它将为我执行此操作。问题是我的新令牌没有 linked 到“主”令牌,所以当主令牌被取消时,我的新令牌仍然有效,直到它超时或建立连接并且它 returns。我想做的是 link 将两个令牌放在一起,这样当主令牌被取消时,我的新令牌也会被取消。我尝试使用 CancellationTokenSource.CreateLinkedTokenSource
方法,但是当我的新令牌超时时,它也取消了主令牌。有没有办法用令牌做我需要做的事情,或者它是否需要更改重试逻辑(可能不会轻易做到这一点)
这是我想要做的:
Master Token – 传递各种功能,以便服务可以干净地关闭。 临时令牌 - 传递给单个函数并设置为一分钟后超时
如果Master Token被取消,Temporary Token也必须被取消。
当临时令牌过期时,它不得取消主令牌。
您想使用 CancellationTokenSource.CreateLinkedTokenSource
。它允许有一个 "parent" 和一个 "child" 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);
预期输出:
Cancel child CTS
Child CTS: True
Parent CTS: FalseCancel parent CTS
Child CTS: True
Parent CTS: True
作为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)
{
}
}
}
When the Temporary Token expires it must NOT cancel the Master Token.
请注意 MyAsyncTask()
的签名接受 CancellationToken
而不是 CancellationTokenSource
。由于该方法只能访问 CancellationToken
上的成员,因此它不会意外取消 master/parent 标记。我建议您以这样一种方式组织代码,即主任务的 CancellationTokenSource
对尽可能少的代码可见。在大多数情况下,这可以通过将 CancellationTokenSource.Token
传递给方法而不是共享对 CancellationTokenSource
.
我没有研究过,但可能有一种方法可以通过反射之类的东西强行取消一个CancellationToken
而不访问它的CancellationTokenSource
。希望这是不可能的,但如果可能的话,它会被认为是不好的做法,通常不必担心。
如果您只有 CancellationToken
,而不是 CancellationTokenSource
,那么仍然可以创建链接的取消令牌。您只需使用 Register
方法来触发(伪)子节点的取消:
var child = new CancellationTokenSource();
token.Register(child.Cancel);
您可以做通常使用 CancellationTokenSource
做的任何事情。例如,您可以在一段时间后取消它,甚至覆盖您之前的令牌。
child.CancelAfter(cancelTime);
token = child.Token;
有几个答案提到从父令牌创建链接令牌源。如果您从其他地方传递了子令牌,则此模式将失效。相反,您可能希望从您的主令牌和传递给您的方法的令牌创建链接令牌源。
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
}
}
}
通常在将令牌传递给方法时,您只能访问取消令牌。要使用其他答案的方法,您可能必须重新管道所有其他方法以传递令牌源。此方法可让您仅使用令牌。