次のコードを考えます:
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
になりますが、そうではありません。私は何かを誤解していますか?
HttpClient
が内部的に(SendAsync
で)TaskCompletionSource
を使用してasync
操作を表すためです。 _TaskCompletionSource.Task
_を返し、それがawait
のタスクです。
次に、_base.SendAsync
_を呼び出し、返されたタスクの継続を登録し、それに応じてTaskCompletionSource
のタスクをキャンセル/完了/フォールトします。
キャンセルの場合、 _TaskCompletionSource.TrySetCanceled
_ を使用します。これは、キャンセルされたタスクを新しいCancellationToken
(default(CancellationToken)
)に関連付けます。
TaskCanceledException
を見ればわかります。 _ex.CancellationToken.IsCancellationRequested
_の上にfalse
である_ex.CancellationToken.CanBeCanceled
_はfalse
でもあります。つまり、このCancellationToken
は、 CancellationTokenSource
。
IMOでは、代わりに TaskCompletionSource.TrySetCanceled(CancellationToken)
を使用する必要があります。そのようにすると、TaskCompletionSource
は、単にデフォルトのCancellationToken
ではなく、コンシューマによって渡されるCancellationToken
に関連付けられます。私はそれがバグだと思います(マイナーなものですが)、それについて 接続に関する問題 を提出しました。
@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;
タイムアウトを無限に設定して無効にし、独自のキャンセルトークンを渡します。
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;
}