忍者ブログ

Memeplexes

プログラミング、3DCGとその他いろいろについて

WPF + WCFでタイムアウト(TimeoutException)

先日WPFとWCFで遊んでいたらこれにはまったのでメモしておきます。

WCFのサーバーをコンソールアプリケーションで作り、クライアントをWPFで作っていたのですが、クライアントからサービスオブジェクトのメソッドを呼ぶと固まって、System.TimeoutExceptionがスローされてしまうのです(WCFでは、デフォルトで1分以内に向こう側から呼ばれたメソッドの処理が終わらないとTimeoutExceptionがスローされます)

実のところこの原因は難しいことではなく、WCFのコールバックオブジェクトのイベント内(UIとは別スレッド)でWPFのButton.Contentを直接変更しようとしたために起きた問題で、Button.Dispatcher.BeginInvokeメソッドで簡単に解決する問題でした。

そうですそうです確かこれはWindows.Formsでも同じことをやった記憶があります。全く進歩がありません。こんなことをブログに書くのは恥ずかしいのですが、ある程度の範囲内なら、より痛い目にあえばより記憶が強力になるような気がしなくもないですからね。

コード

サーバー側のコードには問題がなかったのでクライアント側のコードだけを書きます(恥さらしは少ない方がいい!!)

が、その前にどんな事をしようとしていたのかを簡単に説明しておきましょう。作ろうとしていたのは簡単なゲームで、プレイヤーは2人、それぞれのプレイヤーには最初に20点のライフと自分自身のウィンドウが与えられ、プレイヤーは自分のウィンドウのボタンを押すことで相手のライフを3減らすことができます。自分のライフが0になったら負けなので、ボタンをなるべく連打して相手のライフを早く0にしようとします。遊ぶ時にはそれぞれのプレイヤーは別々のパソコンを使うことになるのでしょう(そのためのWCFです)。そして、ボタンのクリックの速さを競うわけです。

ゲームとしては全然面白くない気がしますが、WCFの練習ならこういうのもいいんじゃないのかなぁと言い訳をしておきます。WPFとWCFに慣れたらもうちょっとフクザツにできるはずですからね。もしかしたらMagic : The Gatheringみたいにできるかもしれません。

で、問題のコードですが、これでした:

player.LifeChanged += delegate
{
    this.YourLifeButton.Content = player.GetLife();
};

playerというのはプレイヤーを表すオブジェクトで、サーバーからのコールバックを受け付けます。WCFではクライアントからサーバーオブジェクトのメソッドを呼び出すことが多いのですが、サーバーからクライアントのオブジェクトのメソッドを呼び出すこともできます。ここではplayerがそのクライアントのオブジェクトで、サーバーはこのplayerのメソッドを呼び出し、ライフを変更したりします。

プレイヤーのライフはウィンドウ上のボタンに表示されます(今思えば全然ボタンである必要はないですね)。なので、プレイヤーのライフが変わった場合にはそのボタンにセットし直すようにプログラムした(つもりだった!)というわけです。

これがまずいのは、このLifeChangedに追加したデリゲートの中身が実行されるのはUIスレッドとは別のスレッドだからですね(サーバーが呼び出すタイミングです)。UIのプロパティの多くは、それを作ったスレッドからしか変更できないからです。詳しいことはMSDNに書いてあります(http://msdn2.microsoft.com/ja-jp/library/ms741870(VS.80).aspxhttp://msdn2.microsoft.com/ja-jp/magazine/cc163328.aspx)。で、まあいろいろあって固まったのでしょう。

正しくは、WPFのDispatcherを使って、その中でButtonを変更しなければならなかったんですね。

player.LifeChanged += delegate
{
    YourLifeButton.Dispatcher.BeginInvoke(
        System.Windows.Threading.DispatcherPriority.Background,
        (System.Action)delegate
        {
            YourLifeButton.Content = player.GetLife();
        }
        );
};


追記

やっぱり全然わかっていませんでした。以上のでどうしようもなかったらクライアントクラスのCallbackBehavior属性のUseSynchronizationContextをfalseにしてみることをおすすめします。

 

拍手[0回]

PR