【お知らせ】プログラミング記事の投稿はQiitaに移行しました。

WebRequestの同期呼び出し

SilverlightのWebRequestには非同期のBeginWebResponseしかありませんが、以下のような拡張メソッドでGetResponse相当の同期呼び出しができます。

メインスレッドからは呼べないので注意が必要です(詳細は後述)。

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

BeginGetResponse()の通知にイベントキューが使われているため、メインスレッドから使用するとWaitOne()で応答がなくなります。そのため処理はサブスレッドから行ってください。

ボタンを押してソースを取得する例は以下の通りです。

private void Button_Click(object sender, RoutedEventArgs e)
{
    new Thread(() =>
    {
        var req = HttpWebRequest.Create("http://127.0.0.1/");
        var res = req.GetResponse();
        var sr = new StreamReader(res.GetResponseStream(), Encoding.UTF8);
        var text = sr.ReadToEnd();
        res.Close();
        Dispatcher.BeginInvoke(() => textBox1.Text = text);
    }).Start();
}

いちいちスレッドを起こすため、取得するデータが1個だと単なる非同期で特にメリットはありません。連続してデータを取得する場合はデータごとにコールバックを用意しなくてもフラットに書けるためメリットがあります。

なお、Silverlightの制限として同一ドメインにしかアクセスすることができません。他のサイトからデータを取得することはできません。

Monitor

別の方法としてMonitorを使った方法があります。AutoResetEventの方が手軽なので、特にこちらを使う必要はないと思います。

public static class WebRequestExtension
{
    public static WebResponse GetResponse(this WebRequest req)
    {
        var mutex = new object();
        WebResponse ret = null;
        lock (mutex)
        {
            req.BeginGetResponse(ar =>
            {
                lock (mutex)
                {
                    ret = req.EndGetResponse(ar);
                    Monitor.PulseAll(mutex);
                }
            }, null);
            Monitor.Wait(mutex);
        }
        return ret;
    }
}