2008-10-14 8 views
138

Comment utiliser HttpWebRequest (.NET, C#) de manière asynchrone?Comment utiliser HttpWebRequest (.NET) de manière asynchrone?

+1

utilisation async http://msdn.microsoft.com/en-us /library/system.net.webrequest.endgetrequeststream.aspx –

+1

pendant un moment, je me demandais si vous essayiez de commenter un fil récursif? –

+0

Vous pouvez également voir ce qui suit, pour un exemple assez complet de faire ce que Jason demande: http://stuff.seans.com/2009/01/05/using-httpwebrequest-for-asynchronous-downloads/ Sean –

Répondre

115

Utilisez HttpWebRequest.BeginGetResponse()

HttpWebRequest webRequest; 

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

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

La fonction de rappel est appelée lorsque l'opération asynchrone est terminée. Vous devez au moins appeler le EndGetResponse() à partir de cette fonction.

+12

BeginGetResponse n'est pas très utile pour l'utilisation asynchrone. Il semble bloquer tout en essayant de contacter la ressource. Essayez de débrancher votre câble réseau ou de lui donner un uri mal formé, puis d'exécuter ce code. Au lieu de cela, vous devrez probablement exécuter GetResponse sur un deuxième thread que vous fournissez. – Ash

+2

@AshleyHenderson - Pourriez-vous s'il vous plaît me fournir un échantillon? – Tohid

+0

@Tohid [Voici une classe complète avec un échantillon] (http://stackoverflow.com/questions/12224602/a-method-for-making-http-requests-on-unity-ios/12606963#12606963) J'ai utilisé avec Unity3D. – cregox

62

Compte tenu de la réponse:

HttpWebRequest webRequest; 

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

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

Vous pouvez envoyer le pointeur de la demande ou tout autre objet comme celui-ci:

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

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

Salutations

+7

+1 pour l'option qui ne dépasse pas la variable 'request', mais que vous auriez pu créer une distribution au lieu d'utiliser le mot clé "as". Une InvalidCastException serait levée à la place d'une confuse NullReferenceException –

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

Tout le monde à ce jour a eu tort, parce que BeginGetResponse() fonctionne un peu sur le thread actuel. De l'documentation:

La méthode BeginGetResponse nécessite certaines tâches de configuration synchrone à complet (résolution DNS, proxy détection et connexion socket TCP, par exemple) avant que cette méthode devient asynchrone. Par conséquent, cette méthode ne doit jamais être appelée sur un thread d'interface utilisateur (UI) car cela peut prendre un certain temps (jusqu'à plusieurs minutes en fonction des paramètres réseau) pour terminer les tâches d'installation synchrones initiales avant une exception pour un erreur est levée ou la méthode réussit.

Donc, pour faire de ce droit:

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

Vous pouvez alors faire ce que vous devez avec la réponse. Par exemple:

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

Pourriez-vous simplement appeler la méthode GetResponseAsync de HttpWebRequest en utilisant await (en supposant que vous ayez fait async de votre fonction)? J'ai très nouveau à C# donc ceci peut être un jibberish complet ... – Brad

+0

GetResponseAsync semble bon, bien que vous ayez besoin de .NET 4.5 (actuellement bêta). – Isak

+12

Jésus. C'est un code moche. Pourquoi le code asynchrone ne peut-il pas être lisible? –

7

Je fini par utiliser BackgroundWorker, il est certainement asynchrone contrairement à certaines des solutions ci-dessus, il gère revenir au fil de l'interface graphique pour vous, et il est très facile à comprendre.

Il est également très facile à manipuler des exceptions, car ils se retrouvent dans la méthode RunWorkerCompleted, mais assurez-vous que vous lisez ceci: Unhandled exceptions in BackgroundWorker

J'utilisé WebClient mais vous pouvez évidemment utiliser HttpWebRequest.GetResponse si vous vouliez.

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

De loin la meilleure façon est à l'aide TaskFactory.FromAsync du TPL. Il est littéralement quelques lignes de code lorsqu'il est utilisé en conjonction avec les nouveaux async/await mots-clés:

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

Si vous ne pouvez pas utiliser le compilateur C# 5 alors ci-dessus peut être accompli en utilisant la méthode Task.ContinueWith:

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

Depuis .NET 4, cette approche TAP est préférable. Voir un exemple similaire de MS - "Comment: envelopper les modèles EAP dans une tâche" (http://msdn.microsoft.com/en-us/library/ee622454.aspx) –

+0

Way plus facile que les autres façons –

2

.NET a changé depuis que beaucoup de ces réponses ont été postées, et j'aimerais apporter une réponse plus à jour. Utilisez une méthode async pour démarrer une Task qui se déroulera sur un thread d'arrière-plan:

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

Pour utiliser la méthode async:

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

Merci! J'ai essayé de trouver un exemple asynchrone, beaucoup d'exemples utilisant une ancienne approche qui est trop complexe. – WDUK

+0

Cela ne bloque-t-il pas un thread pour chaque réponse? il semble assez différent de par exemple. https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/how-to-wrap-eap-patterns-in-a-task –

+0

@PeteKirkham Un thread de fond fait la demande, pas le Fil d'interface utilisateur. L'objectif est d'éviter de bloquer le thread de l'interface utilisateur. Toute méthode que vous choisissez de faire une demande bloquera le thread qui fait la demande. L'exemple Microsoft auquel vous faites référence tente d'effectuer plusieurs requêtes, mais ils créent toujours une tâche (un thread d'arrière-plan) pour les demandes. – tronman

Questions connexes