Come utilizzare HttpWebRequest (.NET) in modo asincrono?


156

Come posso usare HttpWebRequest (.NET, C #) in modo asincrono?


1

Puoi anche vedere quanto segue, per un esempio abbastanza completo di fare quello che Jason sta chiedendo: stuff.seans.com/2009/01/05/… Sean
Sean Sexton


1
per un momento, mi chiedevo se stavi cercando di commentare un thread ricorsivo?
Kyle Hodgson,

Risposte:


125

Uso HttpWebRequest.BeginGetResponse()

HttpWebRequest webRequest;

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

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

La funzione di richiamata viene chiamata al termine dell'operazione asincrona. Devi almeno chiamare EndGetResponse()da questa funzione.


16
BeginGetResponse non è così utile per l'utilizzo asincrono. Sembra bloccarsi durante il tentativo di contattare la risorsa. Prova a scollegare il cavo di rete o assegnargli un uri non corretto, quindi esegui questo codice. Invece è probabilmente necessario eseguire GetResponse su un secondo thread fornito.
Ash,

2
@AshleyHenderson - Potresti fornirmi un campione, per favore?
Tohid

1
@Tohid ecco una classe completa con esempio che ho usato con Unity3D.
Cregox,

3
È necessario aggiungere webRequest.Proxy = nullper accelerare notevolmente la richiesta.
Trontor,

C # genera un errore che mi dice che questa è una classe obsoleta
AleX_

67

Considerando la risposta:

HttpWebRequest webRequest;

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

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

È possibile inviare il puntatore della richiesta o qualsiasi altro oggetto come questo:

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

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

Saluti


7
+1 per l'opzione che non supera l'ambito della variabile 'request', ma avresti potuto creare un cast invece di usare la parola chiave "as". Verrà generata una InvalidCastException anziché una confusa NullReferenceException
Davi Fiamenghi,

64

Finora tutti hanno sbagliato, perché BeginGetResponse()alcuni funzionano sul thread corrente. Dalla documentazione :

Il metodo BeginGetResponse richiede il completamento di alcune attività di configurazione sincrone (risoluzione DNS, rilevamento proxy e connessione socket TCP, ad esempio) prima che questo metodo diventi asincrono. Di conseguenza, questo metodo non deve mai essere chiamato su un thread dell'interfaccia utente (UI) perché potrebbe richiedere molto tempo (fino a diversi minuti a seconda delle impostazioni di rete) per completare le attività di configurazione sincrone iniziali prima che venga generata un'eccezione per un errore o il metodo ha esito positivo.

Quindi per farlo bene:

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

È quindi possibile fare ciò che è necessario con la risposta. Per esempio:

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

2
Non potresti semplicemente chiamare il metodo GetResponseAsync di HttpWebRequest usando wait (supponendo che tu abbia reso la tua funzione asincrona)? Sono molto nuovo in C #, quindi potrebbe essere un vero e proprio jibberish ...
Brad

GetResponseAsync sembra buono, anche se avrai bisogno di .NET 4.5 (attualmente beta).
Isak,

15
Gesù. Questo è un brutto codice. Perché il codice asincrono non può essere letto?
John Shedletsky,

Perché hai bisogno di request.BeginGetResponse ()? Perché wrapperAction.BeginInvoke () non è sufficiente?
Igor Gatis,

2
@Gatis Esistono due livelli di chiamate asincrone: wrapperAction.BeginInvoke () è la prima chiamata asincrona all'espressione lambda che chiama request.BeginGetResponse (), che è la seconda chiamata asincrona. Come sottolinea Isak, BeginGetResponse () richiede una configurazione sincrona, motivo per cui lo avvolge in una chiamata asincrona aggiuntiva.
walkingTarget

64

Di gran lunga il modo più semplice è utilizzare TaskFactory.FromAsync dal TPL . È letteralmente un paio di righe di codice quando utilizzato in combinazione con le nuove parole chiave asincrono / 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 non è possibile utilizzare il compilatore C # 5, è possibile eseguire quanto sopra utilizzando il metodo Task.ContinueWith :

Task.Factory.FromAsync<WebResponse>(request.BeginGetResponse,
                                    request.EndGetResponse,
                                    null)
    .ContinueWith(task =>
    {
        var response = (HttpWebResponse) task.Result;
        Debug.Assert(response.StatusCode == HttpStatusCode.OK);
    });

Da .NET 4 questo approccio TAP è preferibile. Vedi un esempio simile di MS - "Procedura: avvolgere i pattern EAP in un'attività" ( msdn.microsoft.com/en-us/library/ee622454.aspx )
Alex Klaus,

Molto più facile degli altri modi
Don Rolling

8

Ho finito con BackgroundWorker, è decisamente asincrono a differenza di alcune delle soluzioni precedenti, gestisce il ritorno al thread della GUI per te ed è molto facile da capire.

È anche molto facile gestire le eccezioni, poiché finiscono nel metodo RunWorkerCompleted, ma assicurati di leggere questo: Eccezioni non gestite in BackgroundWorker

Ho usato WebClient ma ovviamente potresti usare HttpWebRequest.GetResponse se lo desideri.

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

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

6

.NET è cambiato da quando sono state pubblicate molte di queste risposte e vorrei fornire una risposta più aggiornata. Utilizzare un metodo asincrono per avviare un Taskche verrà eseguito su un thread in background:

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

Per utilizzare il metodo asincrono:

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

Aggiornare:

Questa soluzione non funziona per le app UWP che usano al WebRequest.GetResponseAsync()posto di WebRequest.GetResponse()e non chiama i Dispose()metodi dove appropriato. @dragansr ha una buona soluzione alternativa che risolve questi problemi.


1
Grazie ! Ho cercato di trovare un esempio asincrono, molti esempi usando il vecchio approccio che è troppo complesso.
WDUK,

Questo non bloccherà un thread per ogni risposta? sembra un po 'diverso per es. docs.microsoft.com/en-us/dotnet/standard/parallel-programming/…
Pete Kirkham,

@PeteKirkham Un thread in background sta eseguendo la richiesta, non il thread dell'interfaccia utente. L'obiettivo è evitare di bloccare il thread dell'interfaccia utente. Qualsiasi metodo scelto per effettuare una richiesta bloccherà il thread che effettua la richiesta. L'esempio di Microsoft a cui ti riferisci sta provando a fare più richieste, ma stanno ancora creando un'attività (un thread in background) per le richieste.
tronman,

3
Per essere chiari, questo è un codice sincrono / bloccante al 100%. Per utilizzare async, WebRequest.GetResponseAsync()e StreamReader.ReadToEndAync()devono essere utilizzati e atteso.
Richard Szalay,

4
@tronman L'esecuzione di metodi di blocco in un'attività quando sono disponibili equivalenti asincroni è un anti-pattern altamente scoraggiato. Mentre sblocca il thread chiamante, non fa nulla per la scalabilità per gli scenari di web hosting poiché stai semplicemente spostando il lavoro su un altro thread anziché utilizzare le porte di completamento IO per ottenere l'asincronia.
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);
        } 
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.