Something like this should work (untested):
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;
}
}
}
In theory, if cancellation is requested on ct
and request.Abort
is invoked, await request.GetResponseAsync()
should throw a WebException
. IMO though, it's always a good idea to check for cancellation explicitly when consuming the result, to mitigate race conditions, so I call ct.ThrowIfCancellationRequested()
.
Also, I assume that request.Abort
is thread-safe (can be called from any thread), so I use useSynchronizationContext: false
(I haven't verified that).
[UPDATED] to address the OP's comment on how to differentiate between WebException
caused by cancellation and any other error. This is how it can be done, so TaskCanceledException
(derived from OperationCanceledException
) will be correctly thrown upon cancellation:
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;
}
}
}
}
Abort()
? – MaffickHttpClient
? – Earphone