链接取消令牌

Ret*_*der 32 .net c# cancellationtokensource cancellation-token

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

这是我想要做的:

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

如果取消主令牌,则还必须取消临时令牌.

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

i3a*_*non 56

你想用CancellationTokenSource.CreateLinkedTokenSource.它允许拥有"父母"和"孩子" 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);
Run Code Online (Sandbox Code Playgroud)

按预期输出:

取消子CTS
Child CTS:True
Parent CTS:False

取消父CTS
Child CTS:True
Parent CTS:True

  • 请注意,如果您的代码将许多子令牌源链接到单个父令牌,则应确保处置每个子令牌源以避免内存泄漏。如果不这样做,父级将保持所有子令牌源处于活动状态,因为它具有对每个子令牌源的引用。在许多情况下,这通常就像编写“using var childCts =”一样简单。 (4认同)
  • @Retrocoder 如果您只想捕获内部标记,我建议使用类似`try { doSomething(ct: childCts.Token); 这样的模式。} catch (OperationCancelledException) when (childCts.IsCancellationRequested) {}`。您可以将其放在重试循环中并在循环中创建子令牌源。然后,当父令牌取消时,它会一直冒泡,但是当子令牌取消时,它只会重试。我无法从你的评论中看出——你可能已经在正确地做这件事了 ;-)。 (3认同)
  • 你是对的.我开始使用CancellationTokenSource.CreateLinkedTokenSource,但认为它不起作用.我忘记了当令牌超时时会抛出异常.这在我的代码中被进一步发现.这给人的印象是它没有像我预期的那样工作.通过将我的调用放入try catch块,它工作正常. (2认同)
  • 此外,“OperationCancelledException”有自己的“CancellationToken”属性,您应该使用“catch (ex)when(condition)”方式检查该属性。这样您就可以确保收到正确的取消信息。 (2认同)

bin*_*nki 9

正如i3arnon 已经回答的那样,您可以使用CancellationTokenSource.CreateLinkedTokenSource(). 我想尝试展示一种模式,当您想要区分取消整个任务和取消子任务而不取消整个任务时,如何使用此类标记。

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

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

\n
\n

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

\n

我没有调查过,但可能有一种方法可以通过反射之类的方法强行取消 a 而CancellationToken无需访问其CancellationTokenSource. 希望这是不可能的,但如果可能的话,这将被认为是不好的做法,并且通常不需要担心。

\n


Joh*_*zen 9

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

var child = new CancellationTokenSource();
token.Register(child.Cancel);
Run Code Online (Sandbox Code Playgroud)

您可以执行通常使用CancellationTokenSource. 例如,您可以在一段时间后取消它,甚至覆盖之前的令牌。

child.CancelAfter(cancelTime);
token = child.Token;
Run Code Online (Sandbox Code Playgroud)

  • 实际上,你根本不需要调用 `Register()`。相反,只需使用 CancellationTokenSource.CreateLinkedTokenSource(token),如 i3arnon 接受的答案所示 (5认同)
  • @Matt,如果我们将“using”用于“Register(child.Cancel)”的结果会怎样?像这样:```using var _ = CancellationToken.Register(combinedTokenSource.Cancel);``` (2认同)

The*_*mon 5

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

来自微软的文档: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
      }
  }
}
Run Code Online (Sandbox Code Playgroud)

通常,在将令牌传递给方法时,您只能访问取消令牌。要使用其他答案的方法,您可能必须重新管道所有其他方法以传递令牌源。此方法允许您仅使用令牌。

  • @Matt,已经很久了,我花了一段时间才弄清楚去年的柠檬在想什么 - 其他答案使用父令牌和子令牌源(大多数答案都使用父令牌作为参数生成 childTokenSource )。问题本身是关于链接令牌的,很多用例将要求您链接两个预先存在的令牌,这就是我的答案应该有所帮助的地方 (2认同)