59

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

这是我想做的事情:

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

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

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

4

4 回答 4

97

你想用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);

按预期输出:

取消子 CTS
子 CTS:真
父 CTS:假

取消父 CTS
子 CTS:真
父 CTS:真

于 2015-04-14T09:08:29.260 回答
10

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

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

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

child.CancelAfter(cancelTime);
token = child.Token;
于 2019-03-23T08:54:43.970 回答
5

正如i3arnon 已经回答的那样,您可以使用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()'s 签名接受CancellationToken而不是CancellationTokenSource. 由于该方法只能访问 上的成员CancellationToken,因此它不会意外取消主/父令牌。我建议您以这样一种方式组织您的代码,即CancellationTokenSource主任务的代码对尽可能少的代码可见。在大多数情况下,这可以通过传递CancellationTokenSource.Token给方法而不是共享对CancellationTokenSource.

我没有调查过,但可能有一种类似反射的方法可以强制取消 aCancellationToken而不访问它的CancellationTokenSource. 希望这是不可能的,但如果可能的话,这将被认为是不好的做法,一般不用担心。

于 2017-10-09T19:36:31.757 回答
4

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

来自微软的文档: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) 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
      }
  }
}

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

于 2020-11-16T00:59:40.893 回答