为什么 OperationCancelledException 传送的 CancellationToken 与来自 CTSource 的不同?

Why is OperationCancelledException conveying different CancellationToken than the one from CTSource?

为什么异常中保存的CancellationToken和CancellationTokenSource提供的token不一样?

[Test]
public static async Task SqlCommand_should_recognise_which_CT_triggered_its_cancellation()
{

    var timeout = TimeSpan.FromSeconds(1);
    var cts = new CancellationTokenSource(timeout);

    try
    {
        var connection = new SqlConnection(_config.ConnectionString);
        await connection.OpenAsync(cts.Token);
        var sqlQuery = new SqlCommand("select 1", connection);

        await Task.Delay(timeout + TimeSpan.FromSeconds(1));
        await sqlQuery.ExecuteScalarAsync(cts.Token);
    }
    catch (OperationCanceledException cancelledEx)
    {
        //Shouldn't they be the same?
        Assert.AreEqual(cancelledEx.CancellationToken, cts.Token);
        // The below fails as well
        // Assert.IsTrue(cancelledEx.CancellationToken == cts.Token);


    }
}

Why is the CancellationToken stored in the exception different to token provided by CancellationTokenSource?

这是一个实现细节。我没有查看源代码,但我 怀疑 正在发生的是提供给 ExecuteScalarAsyncCancellationToken 与一些内部 CancellationToken 这意味着 "we lost the connection" 或类似的东西。这些 linked CancellationTokens 不等同于它们的来源 CancellationToken

这通常是 CancellationToken 的使用问题。对于链接令牌,并不总是能够确定 哪个 取消令牌导致了取消。出于这个原因,我建议通过 catch (OperationCanceledException ex) when (cts.IsCancellationRequested):

检查您的 自己的 取消令牌副本
static async Task Main(string[] args)
{
  var timeout = TimeSpan.FromSeconds(1);
  var cts = new CancellationTokenSource(timeout);

  try
  {
    await IndirectDelay(10, cts.Token);

    await Task.Delay(timeout + TimeSpan.FromSeconds(1));
    await IndirectDelay(10, cts.Token);
  }
  catch (OperationCanceledException ex) when (cts.IsCancellationRequested)
  {
    Console.WriteLine(ex.CancellationToken == cts.Token); // false
    Console.WriteLine("Our token is canceled.");
  }
  catch (OperationCanceledException)
  {
    Console.WriteLine("Canceled for some other reason.");
  }
  catch (Exception)
  {
    Console.WriteLine("General error.");
  }
}

private static async Task IndirectDelay(int timeout, CancellationToken token)
{
  using (var internalCts = new CancellationTokenSource())
  using (var cts = CancellationTokenSource.CreateLinkedTokenSource(token, internalCts.Token))
    await Task.Delay(timeout, cts.Token);
}

可能会出现竞争情况,您的代码会 认为 取消是由于超时而发生的,但实际上是因为连接断开(或任何内部逻辑正在发生) on), 但在大多数情况下这无关紧要。