GetResponseAsync does not accept cancellationToken
Asked Answered
B

2

22

It seems that GetResponseAsync does not accept cancellationToken in Async/Await. So the question is how can I cancel the below procedure, provided I need to collect Cookies from response:

 using (HttpWebResponse response = (HttpWebResponse) await request.GetResponseAsync())
 {
    cookies.Add(response.Cookies);
 }

An alternative code to achieve the above is also welcome.

Bushelman answered 6/10, 2013 at 17:54 Comment(4)
Have you tried calling Abort()?Maffick
I've seen it in https://mcmap.net/q/341630/-cancel-an-async-webrequest but not sure how to implement it correctly. I am passing the CancellationToken ct to the method.Bushelman
Why don't you use HttpClient?Earphone
Well it seems #12374238 that it requires more code to write... Anyway why would you prefer HttpClient instead?Bushelman
P
40

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;
            }
        }
    }
}
Ph answered 7/10, 2013 at 1:11 Comment(4)
Couldn't this could be simplified further to ` public static async Task<HttpWebResponse> GetResponseAsync( this HttpWebRequest request, CancellationToken ct) { using (ct.Register(request.Abort, false)) { try { return (HttpWebResponse)await request.GetResponseAsync(); } catch (Exception) { ct.ThrowIfCancellationRequested(); throw; } } }`Cupule
@JosephLennox, you can do so, but you'd loose access to WebException in the client code, which is currently available as Exception.InnerException when cancellation is requested.Ph
In Case this code reaches ct.ThrowIfCancellationRequested(); i think var response should be closed.Wendolyn
@zirbel, you're right, ThrowIfCancellationRequested was racing there with a successful completion of HttpWebRequest.GetResponseAsync. I removed the former, leaving it up to the caller to deal with this race condition (which still was there anyway, for when Extensions.GetResponseAsync possibly completes at the same time the token gets cancelled).Ph
U
10
public static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken, Action action, bool useSynchronizationContext = true)
{
    using (cancellationToken.Register(action, useSynchronizationContext))
    {
        try
        {
            return await task;
        }
        catch (Exception ex)
        {

            if (cancellationToken.IsCancellationRequested)
            {
                // the Exception will be available as Exception.InnerException
                throw new OperationCanceledException(ex.Message, ex, cancellationToken);
            }

            // cancellation hasn't been requested, rethrow the original Exception
            throw;
        }
    }
}

Now you can use your cancellation token on any cancelable async method. For example WebRequest.GetResponseAsync:

var request = (HttpWebRequest)WebRequest.Create(url);

using (var response = await request.GetResponseAsync())
{
    . . .
}

will become:

var request = (HttpWebRequest)WebRequest.Create(url);

using (WebResponse response = await request.GetResponseAsync().WithCancellation(CancellationToken.None, request.Abort, true))
{
    . . .
}

See example http://pastebin.com/KauKE0rW

Ulberto answered 13/10, 2016 at 20:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.