web-dev-qa-db-fra.com

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

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

149
Jason

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 à la fin de l'opération asynchrone. Vous devez au moins appeler EndGetResponse() depuis cette fonction.

121
Jon B

Considérant 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 requête ou tout autre objet du genre:

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

65
xlarsx

Jusqu'ici, tout le monde s'est trompé, car BeginGetResponse() travaille sur le thread actuel. De la documentation :

La méthode BeginGetResponse nécessite certaines tâches d’installation synchrones (résolution DNS, détection de proxy et TCP par exemple)) avant que cette méthode ne devienne asynchrone. En conséquence, elle ne doit jamais être appelée. sur un thread d'interface utilisateur, car l'exécution des tâches d'installation synchrone initiales peut prendre un temps considérable (jusqu'à plusieurs minutes, en fonction des paramètres réseau), puis une exception d'erreur ou la méthode réussit.

Donc, pour faire cela correctement:

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 ensuite faire ce dont vous avez besoin 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);
});
60
Isak

De loin, le moyen le plus simple consiste à utiliser TaskFactory.FromAsync à partir de TPL . Il s'agit littéralement de quelques lignes de code lorsqu'il est utilisé conjointement avec les nouveaux mots-clés 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);

Si vous ne pouvez pas utiliser le compilateur C # 5, vous pouvez utiliser la méthode ci-dessus à l'aide de 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);
    });
57
Nathan Baulch

En fin de compte, j'ai utilisé BackgroundWorker, il est définitivement asynchrone, contrairement à certaines des solutions ci-dessus, il gère le retour au fil de l'interface graphique pour vous, et il est très facile à comprendre.

Il est également très facile de gérer les exceptions, car elles se retrouvent dans la méthode RunWorkerCompleted, mais assurez-vous de lire ceci: Exceptions non gérées dans BackgroundWorker

J'ai utilisé WebClient, mais vous pouvez évidemment utiliser HttpWebRequest.GetResponse si vous le souhaitez.

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
eggbert

.NET a changé depuis que beaucoup de ces réponses ont été publiées et j'aimerais vous donner une réponse plus à jour. Utilisez une méthode async. Pour démarrer un Task qui s'exécutera sur un thread en 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 asynchrone:

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

Mise à jour:

Cette solution ne fonctionne pas pour les applications UWP qui utilisent WebRequest.GetResponseAsync() à la place de WebRequest.GetResponse(), et n'appelle pas les méthodes Dispose() le cas échéant. @dragansr a une bonne solution alternative qui répond à ces problèmes.

5
tronman
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);
        } 
    }
3
Sten Petrov
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);
}
3
dragansr