Подтвердить что ты не робот

GetResponseAsync не принимает cancelationToken

Кажется, что GetResponseAsync не принимает cancelationToken в Async/Await. Итак, вопрос в том, как я могу отменить следующую процедуру, если мне нужно собрать Cookies из ответа:

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

Также приветствуется альтернативный код для достижения вышеуказанного.

4b9b3361

Ответ 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;
            }
        }
    }
}

Ответ 2

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;
        }
    }
}

Теперь вы можете использовать маркер отмены для любого метода отмены async. Например, WebRequest.GetResponseAsync:

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

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

станет:

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

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

См. пример http://pastebin.com/KauKE0rW