酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

HttpWebRequestで同期的に通信を行うことは出来るのか?

Windows PhoneのHttpWebRequestには、同期メソッドが用意されていません。しかし、同期的に処理を行いたいというシーンも当然あると思います。ここでは、Windows Phoneで同期処理が行えるか実験してみたいと思います。

元ネタは確か七誌さんのブログだったと思います。明日起きたら元ネタを探します……

結論としては同期処理は無理。素直にRxを使いましょうです。

AutoResetEventを使って、通信の非同期処理を行っている間はスレッドを止めておいて、シグナルが立ったら抜けて、次の処理が行えるようにするイメージです。

private void button4_Click(object sender, RoutedEventArgs e) {
    var wait = new AutoResetEvent(false);

    var req = HttpWebRequest.CreateHttp("http://www.kantei.go.jp/");
    WebResponse res = null;
    req.BeginGetResponse(ar => {
        res = req.EndGetResponse(ar);
        wait.Set();
    }, null);

    // ここでwait.Set()メソッドが呼ばれるまでスレッドを止める
    wait.WaitOne();

    // ここでresponseを使って好きなことが出来るはず・・・
}

このコードは、一見正しいように見えますが、WaitOneメソッドから戻って来なくなります。BeginGetResponseメソッドの完了通知には、イベントキューが使用されているみたいで、UIスレッドを止めてしまうとAsyncCallbackが実行されず、wait.Setメソッドが呼ばれないからです。

WaitOneメソッドを使う為には、スレッドを起こしてその上で実行する必要があります。HttpWebRequest用の拡張メソッドを使って、以下のようなコードが書けるようになりました。

private void button4_Click(object sender, RoutedEventArgs e) {
    
    // サブスレッドを起こす
    new Thread(() => {

        string text = "";
        var req = HttpWebRequest.CreateHttp("http://www.kantei.go.jp/");
        using (var res = req.GetResponse()) {
            var sr = new StreamReader(res.GetResponseStream(), Encoding.UTF8);
            text = sr.ReadToEnd();
        }

        // TextBlockにテキストを表示するためUIスレッドへInvoke
        Dispatcher.BeginInvoke(() => {
            textBlock1.Text = text;
        });
    }).Start();
}

非同期APIを無理やり同期APIっぽく偽装するHttpWebRequest用の拡張メソッドクラス郡です。

public static class HttpWebRequestExtensions {

    public static WebResponse GetResponse(this HttpWebRequest req) {
        var wait = new AutoResetEvent(false);
        WebResponse ret = null;
        req.BeginGetResponse(ar => {
            ret = req.EndGetResponse(ar);
            wait.Set();
        }, null);
        wait.WaitOne();
        return ret;
    }

    public static Stream GetRequestStream(this HttpWebRequest req) {
        var wait = new AutoResetEvent(false);
        Stream ret = null;
        req.BeginGetRequestStream(ar => {
            ret = req.EndGetRequestStream(ar);
            wait.Set();
        }, null);
        wait.WaitOne();
        return ret;
    }
}

まぁ、素直にRxを使ってください・・・