Почему OperationCancelledException передает сообщение CancellationToken отличное от CTSource? - PullRequest
1 голос
/ 19 апреля 2019

Почему CancellationToken хранится в исключении, отличном от токена, предоставленного CancellationTokenSource?

[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);


    }
}

1 Ответ

2 голосов
/ 19 апреля 2019

Почему CancellationToken хранится в исключении, отличном от токена, предоставленного CancellationTokenSource?

Это деталь реализации. Я не смотрел на источник, но я подозреваю , что CancellationToken, предоставленный ExecuteScalarAsync, объединяется с некоторым внутренним CancellationToken, что означает "мы потеряли соединение" или что-то вроде того. Эти связанные CancellationToken s не эквивалентны их источнику CancellationToken s.

Это проблема использования с CancellationToken s в целом. Со связанными токенами не всегда возможно определить , какой токен отмены вызвал отмену. По этой причине я рекомендую проверить вашу собственную копию токена отмены через 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);
}

Существует вероятность состояния гонки, когда ваш код думает , что отмена происходит из-за тайм-аута, но на самом деле это из-за разрыва соединения (или какой-либо внутренней логики), но в большинстве случаях это не имеет значения.

...