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


156

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


1
Developer Fusionのこの記事を確認してください:developerfusion.com/code/4654/asynchronous-httpwebrequest

また、ジェイソンが求めているものをやってのかなり完全な例は、以下を参照してくださいすることができますstuff.seans.com/2009/01/05/...ショーン
ショーン・セクストン


1
ちょっとの間、私はあなたが再帰的なスレッドにコメントしようとしていたのではないかと思いましたか?
カイルホジソン

回答:


125

使用する HttpWebRequest.BeginGetResponse()

HttpWebRequest webRequest;

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

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

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


16
BeginGetResponseは、非同期の使用にはそれほど役立ちません。リソースに接続しようとしているときにブロックされているようです。ネットワークケーブルを取り外したり、不正な形式のURIを与えたりして、このコードを実行してみてください。代わりに、おそらく提供する2番目のスレッドでGetResponseを実行する必要があります。
アッシュ

2
@AshleyHenderson-サンプルを提供していただけませんか?
Tohid

1
@Tohid は、 Unity3D 使用したサンプル含む完全なクラスです。
cregox 2013年

3
webRequest.Proxy = nullリクエストを劇的にスピードアップするために追加する必要があります。
Trontor 2013年

C#はこれが古いクラスであることを知らせるエラーをスローします
AleX_

67

答えを考える:

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

ご挨拶


7
「リクエスト」変数のスコープを超えないオプションの+1ですが、「as」キーワードを使用する代わりにキャストを作成することもできます。InvalidCastExceptionが混乱の代わりにスローされるNullReferenceException
Davi Fiamenghi

64

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

2
awaitを使用してHttpWebRequestのGetResponseAsyncメソッドを呼び出すだけではありませんか(関数を非同期にしたと想定)。私はC#を初めて使用するので、これは完全に冗談かもしれません...
ブラッド

GetResponseAsyncは問題ありませんが、.NET 4.5(現在はベータ版)が必要です。
Isak 2013

15
イエス。それはいくつかの醜いコードです。なぜ非同期コードが読めないのですか?
John Shedletsky 2013年

なぜrequest.BeginGetResponse()が必要なのですか?なぜwrapperAction.BeginInvoke()では不十分なのですか?
Igor Gatis

2
@Gatis非同期呼び出しには2つのレベルがあります。wrapperAction.BeginInvoke()は、2番目の非同期呼び出しであるrequest.BeginGetResponse()を呼び出すラムダ式への最初の非同期呼び出しです。Isakが指摘するように、BeginGetResponse()は同期セットアップを必要とするため、追加の非同期呼び出しでラップします。
walkingTarget、2015

64

はるかに簡単な方法は、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);
    });

.NET 4以降、このTAPアプローチが推奨されます。MSから同様の例を参照してください- 「どのように:タスクでラップEAPパターン」(msdn.microsoft.com/en-us/library/ee622454.aspx
アレックス・クラウス

他の方法よりもはるかに簡単
Don Rolling

8

結局、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
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は変更されました。より最新の回答を提供したいと思います。非同期メソッドを使用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;
}

asyncメソッドを使用するには:

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

更新:

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


1
ありがとうございました !非同期の例、複雑すぎる古いアプローチを使用した多くの例を見つけようとしてきました。
WDUK、2017年

これは各応答のスレッドをブロックしませんか?それは、例えばにかなりの異なる思わdocs.microsoft.com/en-us/dotnet/standard/parallel-programming/...
ピートKirkham

@PeteKirkham UIスレッドではなく、バックグラウンドスレッドがリクエストを実行しています。目標は、UIスレッドのブロックを回避することです。リクエストを行うために選択したメソッドは、リクエストを行うスレッドをブロックします。参照しているMicrosoftの例は複数のリクエストを作成しようとしていますが、リクエストのタスク(バックグラウンドスレッド)を作成しています。
トロンマン2018年

3
明確にするために、これは100%同期/ブロッキングコードです。非同期を使用するには、WebRequest.GetResponseAsync()およびStreamReader.ReadToEndAync()必要性を使用して待たなければ。
Richard Szalay 2018年

4
@tronman非同期の同等物が利用可能なときにタスクでブロッキングメソッドを実行することは、非常に推奨されないアンチパターンです。呼び出しスレッドのブロックを解除しますが、非同期を実現するためにIO完了ポートを使用するのではなく、単に作業を別のスレッドに移動するだけなので、Webホスティングシナリオのスケーリングには何もしません。
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);
        } 
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.