Как использовать HttpWebRequest (.NET) асинхронно?

156

Как я могу использовать HttpWebRequest (.NET, C #) асинхронно?

Джейсон
источник
1
Проверьте эту статью на Developer Fusion: developerfusion.com/code/4654/asynchronous-httpwebrequest
Вы также можете увидеть следующее, для довольно полного примера выполнения того, что просит Джейсон: stuff.seans.com/2009/01/05/… Шон
Шон Секстон
1
использовать async msdn.microsoft.com/en-us/library/…
Радж
1
на мгновение мне стало интересно, пытались ли вы прокомментировать рекурсивную тему?
Кайл Ходжсон

Ответы:

125

использование HttpWebRequest.BeginGetResponse()

HttpWebRequest webRequest;

void StartWebRequest()
{
    webRequest.BeginGetResponse(new AsyncCallback(FinishWebRequest), null);
}

void FinishWebRequest(IAsyncResult result)
{
    webRequest.EndGetResponse(result);
}

Функция обратного вызова вызывается, когда асинхронная операция завершена. Вам нужно хотя бы позвонить EndGetResponse()из этой функции.

Джон Б
источник
16
BeginGetResponse не так полезен для асинхронного использования. Кажется, блокируется при попытке связаться с ресурсом. Попробуйте отсоединить сетевой кабель или дать ему неверный URI, а затем запустить этот код. Вместо этого вам, вероятно, нужно запустить GetResponse во втором предоставляемом вами потоке.
Пепел
2
@AshleyHenderson - Не могли бы вы предоставить мне образец?
Tohid
1
@ Tohid - это полный класс с образцом, который я использовал с Unity3D.
cregox
3
Вы должны добавить, webRequest.Proxy = nullчтобы ускорить запрос резко.
Тронтор
C # выдает ошибку, сообщая, что это устаревший класс
AleX_
67

Учитывая ответ:

HttpWebRequest webRequest;

void StartWebRequest()
{
    webRequest.BeginGetResponse(new AsyncCallback(FinishWebRequest), null);
}

void FinishWebRequest(IAsyncResult result)
{
    webRequest.EndGetResponse(result);
}

Вы можете отправить указатель запроса или любой другой объект, подобный этому:

void StartWebRequest()
{
    HttpWebRequest webRequest = ...;
    webRequest.BeginGetResponse(new AsyncCallback(FinishWebRequest), webRequest);
}

void FinishWebRequest(IAsyncResult result)
{
    HttpWebResponse response = (result.AsyncState as HttpWebRequest).EndGetResponse(result) as HttpWebResponse;
}

Приветствую

xlarsx
источник
7
+1 для опции, которая не выходит за рамки переменной 'request', но вы могли бы сделать приведение вместо использования ключевого слова as. InvalidCastException будет выброшен вместо дизориентирующему NullReferenceException
Дави Fiamenghi
64

До сих пор все были неправы, потому BeginGetResponse()что работает с текущим потоком. Из документации :

Метод BeginGetResponse требует выполнения некоторых задач синхронной настройки (например, разрешение DNS, обнаружение прокси-сервера и TCP-сокета), прежде чем этот метод станет асинхронным. В результате этот метод никогда не должен вызываться в потоке пользовательского интерфейса (UI), поскольку может потребоваться значительное время (до нескольких минут в зависимости от настроек сети) для выполнения начальных задач синхронной настройки, прежде чем будет сгенерировано исключение для ошибки или метод успешен.

Итак, чтобы сделать это правильно:

void DoWithResponse(HttpWebRequest request, Action<HttpWebResponse> responseAction)
{
    Action wrapperAction = () =>
    {
        request.BeginGetResponse(new AsyncCallback((iar) =>
        {
            var response = (HttpWebResponse)((HttpWebRequest)iar.AsyncState).EndGetResponse(iar);
            responseAction(response);
        }), request);
    };
    wrapperAction.BeginInvoke(new AsyncCallback((iar) =>
    {
        var action = (Action)iar.AsyncState;
        action.EndInvoke(iar);
    }), wrapperAction);
}

Затем вы можете сделать то, что вам нужно с ответом. Например:

HttpWebRequest request;
// init your request...then:
DoWithResponse(request, (response) => {
    var body = new StreamReader(response.GetResponseStream()).ReadToEnd();
    Console.Write(body);
});
Исак
источник
2
Не могли бы вы просто вызвать метод GetResponseAsync HttpWebRequest, используя await (при условии, что вы сделали свою функцию асинхронной)? Я очень новичок в C #, так что это может быть полный бред ...
Брэд
GetResponseAsync выглядит хорошо, хотя вам понадобится .NET 4.5 (в настоящее время бета).
Исак
15
Иисус. Это некрасивый код. Почему асинхронный код не может быть читаемым?
Джон Шедлецкий
Зачем вам нужен запрос. BeginGetResponse ()? Почему wrapperAction.BeginInvoke () не хватает?
Игорь Гатис
2
@Gatis Существует два уровня асинхронных вызовов - wrapperAction.BeginInvoke () - это первый асинхронный вызов лямбда-выражения, который вызывает request.BeginGetResponse (), который является вторым асинхронным вызовом. Как указывает Исак, BeginGetResponse () требует некоторой синхронной настройки, поэтому он оборачивает ее в дополнительный асинхронный вызов.
ходьба Цель
64

Безусловно, самый простой способ - использовать TaskFactory.FromAsync из TPL . Это буквально пара строк кода при использовании в сочетании с новыми ключевыми словами async / await :

var request = WebRequest.Create("http://www.stackoverflow.com");
var response = (HttpWebResponse) await Task.Factory
    .FromAsync<WebResponse>(request.BeginGetResponse,
                            request.EndGetResponse,
                            null);
Debug.Assert(response.StatusCode == HttpStatusCode.OK);

Если вы не можете использовать компилятор C # 5, то вышеперечисленное можно выполнить с помощью метода Task.ContinueWith :

Task.Factory.FromAsync<WebResponse>(request.BeginGetResponse,
                                    request.EndGetResponse,
                                    null)
    .ContinueWith(task =>
    {
        var response = (HttpWebResponse) task.Result;
        Debug.Assert(response.StatusCode == HttpStatusCode.OK);
    });
Натан Баулч
источник
Начиная с .NET 4 этот подход TAP является предпочтительным. Смотрите похожий пример от MS - «Как: обернуть шаблоны EAP в задачу» ( msdn.microsoft.com/en-us/library/ee622454.aspx )
Алекс Клаус
Гораздо проще, чем другие пути
Дон Роллинг
8

В конце концов я использовал BackgroundWorker, он определенно асинхронный в отличие от некоторых из вышеперечисленных решений, он обрабатывает возврат в поток GUI для вас, и его очень легко понять.

Также очень легко обрабатывать исключения, так как они заканчиваются в методе RunWorkerCompleted, но обязательно прочитайте это: Необработанные исключения в BackgroundWorker

Я использовал WebClient, но, очевидно, вы могли бы использовать HttpWebRequest.GetResponse, если хотите.

var worker = new BackgroundWorker();

worker.DoWork += (sender, args) => {
    args.Result = new WebClient().DownloadString(settings.test_url);
};

worker.RunWorkerCompleted += (sender, e) => {
    if (e.Error != null) {
        connectivityLabel.Text = "Error: " + e.Error.Message;
    } else {
        connectivityLabel.Text = "Connectivity OK";
        Log.d("result:" + e.Result);
    }
};

connectivityLabel.Text = "Testing Connectivity";
worker.RunWorkerAsync();
Eggbert
источник
7
public static async Task<byte[]> GetBytesAsync(string url) {
    var request = (HttpWebRequest)WebRequest.Create(url);
    using (var response = await request.GetResponseAsync())
    using (var content = new MemoryStream())
    using (var responseStream = response.GetResponseStream()) {
        await responseStream.CopyToAsync(content);
        return content.ToArray();
    }
}

public static async Task<string> GetStringAsync(string url) {
    var bytes = await GetBytesAsync(url);
    return Encoding.UTF8.GetString(bytes, 0, bytes.Length);
}
dragansr
источник
6

.NET изменился, так как многие из этих ответов были опубликованы, и я хотел бы предоставить более актуальный ответ. Используйте асинхронный метод для запуска Taskв фоновом потоке:

private async Task<String> MakeRequestAsync(String url)
{    
    String responseText = await Task.Run(() =>
    {
        try
        {
            HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
            WebResponse response = request.GetResponse();            
            Stream responseStream = response.GetResponseStream();
            return new StreamReader(responseStream).ReadToEnd();            
        }
        catch (Exception e)
        {
            Console.WriteLine("Error: " + e.Message);
        }
        return null;
    });

    return responseText;
}

Чтобы использовать асинхронный метод:

String response = await MakeRequestAsync("http://example.com/");

Обновить:

Это решение не работает для приложений UWP, которые используют WebRequest.GetResponseAsync()вместо него WebRequest.GetResponse(), и не вызывает Dispose()методы там, где это необходимо. У @dragansr есть хорошее альтернативное решение, которое решает эти проблемы.

tronman
источник
1
Спасибо ! Пытались найти асинхронный пример, множество примеров с использованием старого подхода, который является слишком сложным.
WDUK
Не заблокирует ли это поток для каждого ответа? это выглядит немного иначе, чем, например, docs.microsoft.com/en-us/dotnet/standard/parallel-programming/…
Пит
@PeteKirkham Фоновый поток выполняет запрос, а не поток пользовательского интерфейса. Цель состоит в том, чтобы избежать блокировки потока пользовательского интерфейса. Любой метод, выбранный вами для запроса, блокирует поток, выполняющий запрос. Пример Microsoft, на который вы ссылаетесь, пытается сделать несколько запросов, но они все еще создают Задачу (фоновый поток) для запросов.
Тронман
3
Чтобы быть понятным, это 100% синхронный / блокирующий код. Чтобы использовать асинхронный, WebRequest.GetResponseAsync()и StreamReader.ReadToEndAync()должны быть использованы и ожидаемые.
Ричард Сзалай
4
@tronman Запуск методов блокировки в Задаче, когда асинхронные эквиваленты доступны, является крайне нежелательным анти-паттерном. Хотя он разблокирует вызывающий поток, он ничего не делает для масштабирования для сценариев веб-хостинга, поскольку вы просто перемещаете работу в другой поток, а не используете порты завершения ввода-вывода для достижения асинхронности.
Ричард Сзалай
3
public void GetResponseAsync (HttpWebRequest request, Action<HttpWebResponse> gotResponse)
    {
        if (request != null) { 
            request.BeginGetRequestStream ((r) => {
                try { // there's a try/catch here because execution path is different from invokation one, exception here may cause a crash
                    HttpWebResponse response = request.EndGetResponse (r);
                    if (gotResponse != null) 
                        gotResponse (response);
                } catch (Exception x) {
                    Console.WriteLine ("Unable to get response for '" + request.RequestUri + "' Err: " + x);
                }
            }, null);
        } 
    }
Стен Петров
источник