Ответ 1
Что-то вроде этого должно работать (непроверено):
public static class Extensions
{
public static async Task<HttpWebResponse> GetResponseAsync(this HttpWebRequest request, CancellationToken ct)
{
using (ct.Register(() => request.Abort(), useSynchronizationContext: false))
{
var response = await request.GetResponseAsync();
ct.ThrowIfCancellationRequested();
return (HttpWebResponse)response;
}
}
}
В теории, если требуется отменить отмену в ct
и request.Abort
, await request.GetResponseAsync()
должен выбросить WebException
. ИМО, хотя всегда рекомендуется проверять отмену явно при использовании результата, чтобы смягчить условия гонки, поэтому я звоню ct.ThrowIfCancellationRequested()
.
Кроме того, я предполагаю, что request.Abort
является потокобезопасным (может быть вызван из любого потока), поэтому я использую useSynchronizationContext: false
(я еще не подтвердил это).
[ОБНОВЛЕНО], чтобы ответить на комментарий OP о том, как различать WebException
, вызванные отменой и любой другой ошибкой. Вот как это можно сделать, поэтому TaskCanceledException
(полученный из OperationCanceledException
) будет правильно выбрано при аннулировании:
public static class Extensions
{
public static async Task<HttpWebResponse> GetResponseAsync(this HttpWebRequest request, CancellationToken ct)
{
using (ct.Register(() => request.Abort(), useSynchronizationContext: false))
{
try
{
var response = await request.GetResponseAsync();
return (HttpWebResponse)response;
}
catch (WebException ex)
{
// WebException is thrown when request.Abort() is called,
// but there may be many other reasons,
// propagate the WebException to the caller correctly
if (ct.IsCancellationRequested)
{
// the WebException will be available as Exception.InnerException
throw new OperationCanceledException(ex.Message, ex, ct);
}
// cancellation hasn't been requested, rethrow the original WebException
throw;
}
}
}
}