Windows Phone でWebClient() でのTimeout 例外処理について

Windows Phone でHTTP通信をおこなうとき、ただ単に通信をおこなうのではなく、エラー処理なども考慮して作成しておく必要があります。

今回はHTTP通信の際に、指定時間以内に通信結果が取得できなければ発生するTimeout Exceotion (タイムアウト)について記述します。

Windows Phone のHTTP通信は2種類の方法があります。

  • HttpWebRequestクラスによる通信
  • WebClientクラスによる通信

タイムアウト処理についてggった結果、いっこうに見つかりません。


そこで伝家の宝刀【Twitter】で、先人の知恵をお借りすることに...

その結果なんと、HttpWebRequestクラスとWebClientクラスには、Timeoutプロパティが存在しないらしい

自分で実装すると、ソースコードがごちゃごちゃになってしまう可能性があるので、また何か使えないかggった結果
Reactive Extensions
というのにたどり着きました

Reactive Extensions( Rx )については、現在勉強中ですが、なにやらイベントや非同期についての処理ライブラリで、Windows Phone SDK に、すでに組み込まれているものらしいです。

※ このライブラリを使用するには、Microsoft.Phone.ReactiveSystem.Observableを参照しておく必要があります


このReactive Extensions には、豊富な例外処理手段が用意されています。

今回は、目的のとおり、Timeout Exception (タイムアウト)についておこないます


次のコードは、HTTP通信(非同期通信)時にTimeoutException をCatch して、Exception の派生クラスでログを出力するプログラムになっています。

public class HttpUtil
{
    public static void getHttpResult()
    {
        // 接続先URI
        string uri = "http://.......";

        // タイムアウト時間
        int timeout = 100; // 100ms

        // URIで識別されるリソースとの間でデータを送受信する共通メソッド
        var cli = new WebClient();

        // 非同期通信イベントをReactive Extensionオブジェクトに変換する
        var obs = Observable.FromEvent<DownloadStringCompletedEventHandler, DownloadStringCompletedEventArgs>(
                  h => h.Invoke, h => cli.DownloadStringCompleted += h, h => cli.DownloadStringCompleted -= h);

        // イベントの結果を関連付けて、返り値を取得する
        obs.Select(evt => evt.EventArgs.Result)
           .Timeout(TimeSpan.FromMilliseconds(timeout)) // タイムアウト時間の設定
           .Take(1) // Timeoutチェック回数を1回に設定
           .Catch((TimeoutException e) => MyTimeoutException("タイムアウト", e))
           .ObserveOnDispatcher() // MessageBoxの表示はUIスレッド上でないといけないので、処理をUIスレッドに戻す
           .Subscribe(msg => MessageBox.Show(msg));

        // 非同期通信を開始する
        cli.DownloadStringAsync(new Uri(uri));
    }

    private static IObservable<string> MyException(string message, TimeoutException e)
    {
        new MyException(message, e);
        // 戻り値として何もしないEmptyを返す
        return Observable.Empty<string>();
    }
}

public class MyException: Exception
{
    public MyException: (string message, Exception innerException) : base(message, innerException)
    {
	// タイムアウト例外発生をログに出力
    }
}

timeout で指定した時間以内に通信結果が返らなければ、TimeoutException が発生し、その例外をキャッチする形になっています。


ここでの注意点ですが...

  1. 例外をキャッチ後に、再スローしてはいけない → MyException was unhandledというエラーがでてしまう
    追加で例外をthrowしてしまうことで、App.xaml.csのApplication_UnhandledExceptionに拾われてしまって、正常に動作しない

    ※ Application_UnhandledExceptionは、アプリケーションでハンドルされていない例外が発生したときにコールされるメソッド

  2. DownloadStringAsyncはSubscribeの後のほうが望ましい → タイマー設定前に通信が発生してしまうかもしれない
    DownloadStringAsyncをコールした段階で非同期通信が始まっているので、最悪タイマー設定する前にDownloadStringCompletedイベントが発生してしまうかもしれない

  3. MessageBoxの表示の際は、ObserveOnDispatcher メソッドをCallする
    MessageBoxの表示はUIスレッド上でないといけないので、処理をUIスレッドに戻すObserveOnDispatcher メソッドが必要である

これらの注意点は、neueccさんtanaka_733さん大黒健司さんからご説明いただきましたものを引用させて頂いております。
http://social.msdn.microsoft.com/Forums/ja-JP/wpdevja/thread/4e172153-46d1-452f-84ad-f1a3e0775a78



http://www.st-hatena.com/users/sa/samril/user_p.gif皆様のおかげで、また1つ学ぶことができました!
感謝です☆