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

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

10 ответов

Решение

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

HttpWebRequest webRequest;

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

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

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

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

var request = WebRequest.Create("http://www.stackru.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);
    });

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

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

Привет

Все до сих пор был неправ, потому что 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);
});
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);
}

В конце концов я использовал 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();

.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 есть хорошее альтернативное решение, которое решает эти проблемы.

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

Следуйте ответу @Isak, что очень хорошо. Тем не менее, его самый большой недостаток заключается в том, что он будет вызывать responseAction только в том случае, если ответ имеет статус 200-299. Лучший способ исправить это:

      private void DoWithResponseAsync(HttpWebRequest request, Action<HttpWebResponse> responseAction)
{
    Action wrapperAction = () =>
    {
        request.BeginGetResponse(new AsyncCallback((iar) =>
        {
            HttpWebResponse response;
            try
            {
                response = (HttpWebResponse)((HttpWebRequest)iar.AsyncState).EndGetResponse(iar);
            }
            catch (WebException ex)
            {
                // It needs to be done like this in order to read responses with error status:
                response = ex.Response as HttpWebResponse;
            }
            responseAction(response);
        }), request);
    };
    wrapperAction.BeginInvoke(new AsyncCallback((iar) =>
    {
        var action = (Action)iar.AsyncState;
        action.EndInvoke(iar);
    }), wrapperAction);
}

А затем, как следует @Isak:

      HttpWebRequest request;
// init your request...then:
DoWithResponse(request, (response) => {
    var body = new StreamReader(response.GetResponseStream()).ReadToEnd();
    Console.Write(body);
});

Я использовал это для асинхронного UWR, надеюсь, это кому-то поможет.

          string uri = "http://some.place.online";

    using (UnityWebRequest uwr = UnityWebRequest.Get(uri))
    {
        var asyncOp = uwr.SendWebRequest();
        while (asyncOp.isDone == false) await Task.Delay(1000 / 30); // 30 hertz

        if(uwr.result == UnityWebRequest.Result.Success) return uwr.downloadHandler.text;
        Debug.LogError(uwr.error);
    }
Другие вопросы по тегам