web-dev-qa-db-ja.com

HttpWebRequest(.NET)を非同期で使用する方法

HttpWebRequest(.NET、C#)を非同期で使用するにはどうすればよいですか?

149
Jason

HttpWebRequest.BeginGetResponse() を使用します

HttpWebRequest webRequest;

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

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

非同期操作が完了すると、コールバック関数が呼び出されます。少なくともこの関数から EndGetResponse() を呼び出す必要があります。

121
Jon B

答えを考える:

HttpWebRequest webRequest;

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

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

リクエストポインターまたは次のような他のオブジェクトを送信できます。

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

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

あいさつ

65
xlarsx

BeginGetResponse()は現在のスレッドでいくつかの作業を行うため、これまでのところ誰もが間違っています。 ドキュメント から:

BeginGetResponseメソッドでは、このメソッドが非同期になる前に、いくつかの同期セットアップタスク(DNS解決、プロキシ検出、TCPソケット接続など)を完了する必要があります。そのため、エラーの例外がスローされる前に初期同期セットアップタスクを完了するのにかなりの時間(ネットワーク設定によっては最大数分)がかかる可能性があるため、このメソッドをユーザーインターフェイス(UI)スレッドで呼び出さないでください。メソッドは成功します。

したがって、これを正しく行うには:

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

その後、応答で必要なことを実行できます。例えば:

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

最も簡単な方法は、 TPL から TaskFactory.FromAsync を使用することです。新しい async/await キーワードと組み合わせて使用​​すると、文字通り数行のコードになります。

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

C#5コンパイラを使用できない場合、 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

私はBackgroundWorkerを使用することになりました。上記のソリューションのいくつかとは異なり、間違いなく非同期であり、GUIスレッドへの戻りを処理し、非常に理解しやすいです。

例外はRunWorkerCompletedメソッドで終了するため、例外の処理も非常に簡単ですが、必ずこれを読んでください: BackgroundWorkerの未処理の例外

私はWebClientを使用しましたが、明らかに必要であればHttpWebRequest.GetResponseを使用できます。

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が変わりました。最新の回答を提供したいと思います。非同期メソッドを使用して、バックグラウンドスレッドで実行されるTaskを開始します。

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

非同期メソッドを使用するには:

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

更新:

このソリューションは、WebRequest.GetResponseAsync()の代わりにWebRequest.GetResponse()を使用するUWPアプリでは機能せず、必要に応じてDispose()メソッドを呼び出しません。 @dragansrには、これらの問題に対処する優れた代替ソリューションがあります。

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