web-dev-qa-db-ja.com

HttpClientリクエストのキャンセル-TaskCanceledException.CancellationToken.IsCancellationRequestedがfalseなのはなぜですか?

次のコードを考えます:

var cts = new CancellationTokenSource();

try 
{
    // get a "hot" task
    var task = new HttpClient().GetAsync("http://www.google.com", cts.Token);

    // request cancellation
    cts.Cancel();

    await task;

    // pass:
    Assert.Fail("expected TaskCanceledException to be thrown");
}
catch (TaskCanceledException ex) 
{
    // pass:
    Assert.IsTrue(cts.Token.IsCancellationRequested,
        "expected cancellation requested on original token");

    // fail:
    Assert.IsTrue(ex.CancellationToken.IsCancellationRequested,
        "expected cancellation requested on token attached to exception");
}

ex.CancellationToken.IsCancellationRequestedはcatchブロック内でtrueになりますが、そうではありません。私は何かを誤解していますか?

30
Todd Menier

HttpClientが内部的に(SendAsyncで)TaskCompletionSourceを使用してasync操作を表すためです。 _TaskCompletionSource.Task_を返し、それがawaitのタスクです。

次に、_base.SendAsync_を呼び出し、返されたタスクの継続を登録し、それに応じてTaskCompletionSourceのタスクをキャンセル/完了/フォールトします。

キャンセルの場合、 _TaskCompletionSource.TrySetCanceled_ を使用します。これは、キャンセルされたタスクを新しいCancellationTokendefault(CancellationToken))に関連付けます。

TaskCanceledExceptionを見ればわかります。 _ex.CancellationToken.IsCancellationRequested_の上にfalseである_ex.CancellationToken.CanBeCanceled_はfalseでもあります。つまり、このCancellationTokenは、 CancellationTokenSource


IMOでは、代わりに TaskCompletionSource.TrySetCanceled(CancellationToken) を使用する必要があります。そのようにすると、TaskCompletionSourceは、単にデフォルトのCancellationTokenではなく、コンシューマによって渡されるCancellationTokenに関連付けられます。私はそれがバグだと思います(マイナーなものですが)、それについて 接続に関する問題 を提出しました。

43
i3arnon

@Bengieこれは私にはうまくいきませんでした。少し変更しなければなりませんでした。 IsCancellationRequestedは常にtrueを返したので、それに頼ることはできませんでした。

これは私のために働いた:

using (CancellationTokenSource cancelAfterDelay = new CancellationTokenSource(TimeSpan.FromSeconds(timeout)))
{
    DateTime startedTime = DateTime.Now;

    try
    {
        response = await request.ExecuteAsync(cancelAfterDelay.Token);
    }
    catch (TaskCanceledException e)
    {
        DateTime cancelledTime = DateTime.Now;
        if (startedTime.AddSeconds(timeout-1) <= cancelledTime)
        {
            throw new TimeoutException($"An HTTP request to {request.Url} timed out ({timeout} seconds)");
        }
        else
            throw;
    }
}
return response;
1
Janett Holst

タイムアウトを無限に設定して無効にし、独自のキャンセルトークンを渡します。

using(CancellationTokenSource cancelAfterDelay = new CancellationTokenSource(timespan/timeout))
...
catch(OperationCanceledException e)
{
if(!cancelAfterDelay.Token.IsCancellationRequested)
throw new TimeoutException($"An HTTP request to {request.Uri} timed out ({(int)requestTimeout.TotalSeconds} seconds)");
else
throw;
}
0
Bengie