Wie verwende ich HttpWebRequest (.NET) asynchron?

156

Wie kann ich HttpWebRequest (.NET, C #) asynchron verwenden?

Jason
quelle
1
Sie können auch das Folgende sehen, um ein ziemlich vollständiges Beispiel dafür zu sehen, was Jason verlangt: stuff.seans.com/2009/01/05/… Sean
Sean Sexton
1
Verwenden Sie async msdn.microsoft.com/en-us/library/…
Raj Kaimal
1
Für einen Moment habe ich mich gefragt, ob Sie versuchen, einen rekursiven Thread zu kommentieren.
Kyle Hodgson

Antworten:

125

Verwenden HttpWebRequest.BeginGetResponse()

HttpWebRequest webRequest;

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

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

Die Rückruffunktion wird aufgerufen, wenn der asynchrone Vorgang abgeschlossen ist. Sie müssen mindestens EndGetResponse()von dieser Funktion aus aufrufen .

Jon B.
quelle
16
BeginGetResponse ist für die asynchrone Verwendung nicht so nützlich. Es scheint zu blockieren, während versucht wird, die Ressource zu kontaktieren. Ziehen Sie das Netzkabel ab oder geben Sie ihm eine fehlerhafte URL, und führen Sie dann diesen Code aus. Stattdessen müssen Sie GetResponse wahrscheinlich auf einem zweiten von Ihnen bereitgestellten Thread ausführen.
Ash
2
@AshleyHenderson - Könnten Sie mir bitte ein Muster zur Verfügung stellen?
Tohid
1
@Tohid hier ist eine vollständige Klasse mit Beispiel, das ich mit Unity3D verwendet habe.
Cregox
3
Sie sollten hinzufügen webRequest.Proxy = null, um die Anforderung drastisch zu beschleunigen.
Trontor
C # wirft einen Fehler aus, der mir sagt, dass dies eine veraltete Klasse ist
AleX_
67

Betrachtet man die Antwort:

HttpWebRequest webRequest;

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

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

Sie können den Anforderungszeiger oder ein anderes Objekt wie folgt senden:

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

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

Schöne Grüße

xlarsx
quelle
7
+1 für die Option, die die Variable 'request' nicht überschreitet, aber Sie hätten eine Umwandlung vornehmen können, anstatt das Schlüsselwort "as" zu verwenden. Eine InvalidCastException würde anstelle einer verwirrenden NullReferenceException
Davi Fiamenghi
64

Bisher haben sich alle geirrt, weil BeginGetResponse()einige am aktuellen Thread arbeiten. Aus der Dokumentation :

Für die BeginGetResponse-Methode müssen einige synchrone Setup-Aufgaben ausgeführt werden (z. B. DNS-Auflösung, Proxy-Erkennung und TCP-Socket-Verbindung), bevor diese Methode asynchron wird. Daher sollte diese Methode niemals in einem UI-Thread (User Interface) aufgerufen werden, da es einige Zeit (bis zu mehreren Minuten, abhängig von den Netzwerkeinstellungen) dauern kann, bis die anfänglichen synchronen Setup-Aufgaben abgeschlossen sind, bevor eine Ausnahme für einen Fehler ausgelöst wird oder Die Methode ist erfolgreich.

Um dies richtig zu machen:

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

Mit der Antwort können Sie dann tun, was Sie brauchen. Beispielsweise:

HttpWebRequest request;
// init your request...then:
DoWithResponse(request, (response) => {
    var body = new StreamReader(response.GetResponseStream()).ReadToEnd();
    Console.Write(body);
});
Isak
quelle
2
Könnten Sie nicht einfach die GetResponseAsync-Methode der HttpWebRequest mit await aufrufen (vorausgesetzt, Sie haben Ihre Funktion asynchron gemacht)? Ich bin sehr neu in C #, daher ist dies möglicherweise ein kompletter Quatsch ...
Brad,
GetResponseAsync sieht gut aus, obwohl Sie .NET 4.5 (derzeit Beta) benötigen.
Isak
15
Jesus. Das ist ein hässlicher Code. Warum kann asynchroner Code nicht gelesen werden?
John Shedletsky
Warum brauchen Sie request.BeginGetResponse ()? Warum reicht wrapperAction.BeginInvoke () nicht aus?
Igor Gatis
2
@Gatis Es gibt zwei Ebenen von asynchronen Aufrufen: wrapperAction.BeginInvoke () ist der erste asynchrone Aufruf des Lambda-Ausdrucks, der request.BeginGetResponse () aufruft. Dies ist der zweite asynchrone Aufruf. Wie Isak betont, erfordert BeginGetResponse () ein synchrones Setup, weshalb er es in einen zusätzlichen asynchronen Aufruf einschließt.
WalkingTarget
64

Der mit Abstand einfachste Weg ist die Verwendung von TaskFactory.FromAsync aus der TPL . In Verbindung mit den neuen Schlüsselwörtern async / await sind es buchstäblich ein paar Codezeilen :

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

Wenn Sie den C # 5-Compiler nicht verwenden können, können Sie die oben genannten Schritte mit der Task.ContinueWith- Methode ausführen :

Task.Factory.FromAsync<WebResponse>(request.BeginGetResponse,
                                    request.EndGetResponse,
                                    null)
    .ContinueWith(task =>
    {
        var response = (HttpWebResponse) task.Result;
        Debug.Assert(response.StatusCode == HttpStatusCode.OK);
    });
Nathan Baulch
quelle
Seit .NET 4 ist dieser TAP-Ansatz vorzuziehen. Siehe ein ähnliches Beispiel aus MS - " Gewusst wie: Umschließen von EAP- Mustern in eine Aufgabe" ( msdn.microsoft.com/en-us/library/ee622454.aspx )
Alex Klaus
Viel einfacher als die anderen Wege
Don Rolling
8

Am Ende habe ich BackgroundWorker verwendet. Im Gegensatz zu einigen der oben genannten Lösungen ist es definitiv asynchron. Es übernimmt die Rückkehr zum GUI-Thread für Sie und ist sehr einfach zu verstehen.

Es ist auch sehr einfach, Ausnahmen zu behandeln, da sie in der RunWorkerCompleted-Methode enden. Lesen Sie jedoch Folgendes : Nicht behandelte Ausnahmen in BackgroundWorker

Ich habe WebClient verwendet, aber natürlich können Sie HttpWebRequest.GetResponse verwenden, wenn Sie möchten.

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

.NET hat sich geändert, seit viele dieser Antworten veröffentlicht wurden, und ich möchte eine aktuellere Antwort geben. Verwenden Sie eine asynchrone Methode, um eine Methode zu starten Task, die in einem Hintergrundthread ausgeführt wird:

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

So verwenden Sie die asynchrone Methode:

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

Aktualisieren:

Diese Lösung funktioniert nicht für UWP-Apps, die WebRequest.GetResponseAsync()anstelle von verwenden WebRequest.GetResponse(), und ruft die Dispose()Methoden gegebenenfalls nicht auf. @dragansr hat eine gute alternative Lösung, die diese Probleme behebt.

Tronman
quelle
1
Danke ! Ich habe versucht, ein asynchrones Beispiel zu finden, viele Beispiele mit einem alten Ansatz, der überkomplex ist.
WDUK
Blockiert dies nicht einen Thread für jede Antwort? es scheint ein bisschen anders zu sein als zB docs.microsoft.com/en-us/dotnet/standard/parallel-programming/…
Pete Kirkham
@PeteKirkham Ein Hintergrund-Thread führt die Anforderung aus, nicht der UI-Thread. Ziel ist es, das Blockieren des UI-Threads zu vermeiden. Jede Methode, mit der Sie eine Anfrage stellen, blockiert den Thread, der die Anfrage stellt. In dem Microsoft-Beispiel, auf das Sie verweisen, wird versucht, mehrere Anforderungen zu stellen, es wird jedoch weiterhin eine Aufgabe (ein Hintergrundthread) für die Anforderungen erstellt.
Tronman
3
Dies ist zu 100% ein Synchron- / Blockierungscode. Zur Nutzung async, WebRequest.GetResponseAsync()und StreamReader.ReadToEndAync()müssen verwendet werden und abgewartet.
Richard Szalay
4
@tronman Das Ausführen von Blockierungsmethoden in einer Task, wenn asynchrone Entsprechungen verfügbar sind, ist ein stark entmutigtes Anti-Pattern. Der aufrufende Thread wird zwar entsperrt, für Webhosting-Szenarien jedoch nicht skaliert, da Sie die Arbeit nur auf einen anderen Thread verschieben, anstatt E / A-Abschlussports zu verwenden, um die Asynchronität zu erreichen.
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
quelle