Como usar o HttpWebRequest (.NET) assincronamente?

156

Como posso usar o HttpWebRequest (.NET, C #) de forma assíncrona?

Jason
fonte
1
Confira este artigo no Developer Fusion: developerfusion.com/code/4654/asynchronous-httpwebrequest
Você também pode ver o seguinte, para um exemplo bastante completo de fazer o que Jason está pedindo: stuff.seans.com/2009/01/05/... Sean
Sean Sexton
1
use async msdn.microsoft.com/en-us/library/…
Raj Kaimal
1
por um momento, me perguntei se você estava tentando comentar um tópico recursivo?
Kyle Hodgson

Respostas:

125

Usar HttpWebRequest.BeginGetResponse()

HttpWebRequest webRequest;

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

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

A função de retorno de chamada é chamada quando a operação assíncrona estiver concluída. Você precisa pelo menos ligar a EndGetResponse()partir desta função.

Jon B
fonte
16
BeginGetResponse não é tão útil para uso assíncrono. Parece bloquear ao tentar entrar em contato com o recurso. Tente desconectar o cabo de rede ou fornecer uma uri malformada e execute esse código. Em vez disso, você provavelmente precisará executar a GetResponse em um segundo segmento fornecido.
Ash
2
@AshleyHenderson - Você poderia me fornecer uma amostra?
Tohid
1
@ Ohh aqui é uma classe completa com amostra que eu usei com o Unity3D.
Cregox 27/05
3
Você deve adicionar webRequest.Proxy = nullpara acelerar drasticamente a solicitação.
Trontor 30/10/2013
C # gera um erro dizer-me que esta é uma classe obsoleta
AleX_
67

Considerando a resposta:

HttpWebRequest webRequest;

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

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

Você pode enviar o ponteiro da solicitação ou qualquer outro objeto como este:

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

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

saudações

xlarsx
fonte
7
+1 para a opção que não excede o escopo da variável 'request', mas você poderia ter feito uma conversão em vez de usar a palavra-chave "as". Uma InvalidCastException seria lançada em vez de uma confusão NullReferenceException
Davi Fiamenghi
64

Todo mundo até agora está errado, porque BeginGetResponse()faz algum trabalho no segmento atual. A partir da documentação :

O método BeginGetResponse requer que algumas tarefas de configuração síncrona sejam concluídas (resolução DNS, detecção de proxy e conexão de soquete TCP, por exemplo) antes que esse método se torne assíncrono. Como resultado, esse método nunca deve ser chamado em um encadeamento da interface do usuário, pois pode levar um tempo considerável (até vários minutos, dependendo das configurações da rede) para concluir as tarefas de configuração síncrona inicial antes que uma exceção para um erro seja lançada ou o método é bem sucedido.

Então, para fazer isso direito:

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

Você pode fazer o que precisar com a resposta. Por exemplo:

HttpWebRequest request;
// init your request...then:
DoWithResponse(request, (response) => {
    var body = new StreamReader(response.GetResponseStream()).ReadToEnd();
    Console.Write(body);
});
Isak
fonte
2
Você não pode simplesmente chamar o método GetResponseAsync do HttpWebRequest usando waitit (supondo que você tenha feito sua função assíncrona)? Eu tenho muito novo para C # assim que este pode ser jibberish completo ...
Brad
GetResponseAsync parece ser bom, embora você precise do .NET 4.5 (atualmente beta).
21313 Isak
15
Jesus. Esse é um código feio. Por que o código assíncrono não pode ser lido?
John Shedletsky
Por que você precisa request.BeginGetResponse ()? Por que wrapperAction.BeginInvoke () não é suficiente?
Igor Gatis
2
@Gatis Existem dois níveis de chamadas assíncronas - wrapperAction.BeginInvoke () é a primeira chamada assíncrona à expressão lambda que chama request.BeginGetResponse (), que é a segunda chamada assíncrona. Como Isak aponta, BeginGetResponse () requer alguma configuração síncrona, e é por isso que ele a envolve em uma chamada assíncrona adicional.
walkingTarget
64

De longe, a maneira mais fácil é usar o TaskFactory.FromAsync da TPL . É literalmente algumas linhas de código quando usadas em conjunto com as novas palavras-chave async / wait :

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

Se você não pode usar o compilador C # 5, o procedimento acima pode ser realizado usando o método Task.ContinueWith :

Task.Factory.FromAsync<WebResponse>(request.BeginGetResponse,
                                    request.EndGetResponse,
                                    null)
    .ContinueWith(task =>
    {
        var response = (HttpWebResponse) task.Result;
        Debug.Assert(response.StatusCode == HttpStatusCode.OK);
    });
Nathan Baulch
fonte
Desde o .NET 4, essa abordagem TAP é preferível. Veja um exemplo semelhante de MS - "How to: Patterns Enrole EAP em uma tarefa" ( msdn.microsoft.com/en-us/library/ee622454.aspx )
Alex Klaus
Caminho mais fácil do que as outras maneiras
Don Rolando
8

Acabei usando o BackgroundWorker, é definitivamente assíncrono, ao contrário de algumas das soluções acima, ele lida com o retorno ao thread da GUI para você e é muito fácil de entender.

Também é muito fácil lidar com exceções, pois elas acabam no método RunWorkerCompleted, mas certifique-se de ler o seguinte: Exceções não tratadas no BackgroundWorker

Eu usei o WebClient, mas obviamente você poderia usar HttpWebRequest.GetResponse, se quisesse.

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
fonte
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
fonte
6

O .NET mudou desde que muitas dessas respostas foram postadas e eu gostaria de fornecer uma resposta mais atualizada. Use um método assíncrono para iniciar um Taskque será executado em um encadeamento em segundo plano:

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

Para usar o método assíncrono:

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

Atualizar:

Esta solução não funciona para aplicativos UWP que usam em WebRequest.GetResponseAsync()vez de WebRequest.GetResponse()e não chama os Dispose()métodos quando apropriado. O @dragansr tem uma boa solução alternativa que resolve esses problemas.

tronman
fonte
1
Obrigado ! Estão tentando encontrar um exemplo assíncrono, muitos exemplos usando a abordagem antiga, que é mais complexa.
WDUK
Isso não bloqueará um thread para cada resposta? parece um pouco diferente, por exemplo, docs.microsoft.com/en-us/dotnet/standard/parallel-programming/…
Pete Kirkham
@PeteKirkham Um thread em segundo plano está fazendo a solicitação, não o thread da interface do usuário. O objetivo é evitar o bloqueio do thread da interface do usuário. Qualquer método que você escolher para fazer uma solicitação bloqueará o thread que está fazendo a solicitação. O exemplo da Microsoft a que você se refere está tentando fazer várias solicitações, mas elas ainda estão criando uma tarefa (um encadeamento em segundo plano) para as solicitações.
tronman
3
Para ser claro, esse é um código 100% síncrono / de bloqueio. Para usar assíncrono, WebRequest.GetResponseAsync()e StreamReader.ReadToEndAync()precisa ser usado e aguardado.
Richard Szalay
4
@tronman A execução de métodos de bloqueio em uma Tarefa quando equivalentes assíncronos estão disponíveis é um anti-padrão altamente desencorajado. Embora ele desbloqueie o encadeamento de chamada, ele não faz escala para cenários de hospedagem na Web, pois você está apenas movendo o trabalho para outro encadeamento, em vez de usar portas de conclusão de E / S para obter a assincronia.
Richard Szalay
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);
        } 
    }
Sten Petrov
fonte