かぶだいくんのクリック証券の Web サービスの HTML アクセスを非同期でコーディングした

id:unibon:20081224:p2 の続き。
今までお手軽に、.NET の HttpWebRequest クラスを使い、HTTP の Request から Response までをひとつのメソッド内でおこなっていたので、サーバーからの応答が返るまでの間、アプリケーションに制御が戻らない状態だった。クリック証券のサーバーはわりと速いので、その時間は1秒未満であり、それほどは気にならないものの、やはり同期でアクセスしているというのは、コーディングとしてはしょぼい。たとえば、その間、フォームのボタンなどが押せない。そう思って、非同期でアクセスするようにコーディングし直してみたが、これが予想どおり結構大変。


unibon はあまりネットワークプログラミングには詳しくなく、HTTP っていうものは、Request と Response の2つのフェーズなのかなと思っていたが、HttpWebRequest クラスの単位だと、

  • Request Header の送信(参考にしたサイトの Request メソッドに相当する)
  • Request Body の Stream の書き込み(参考にしたサイトの WriteCallBack メソッドに相当する)
  • Response Header の受信と Response Body の Stream の読み込み (参考にしたサイトの ReadCallBack メソッドに相当する)

の3つに分割して処理しないといけないようだ。


あと、最後に Response Body の Stream を読み込んだ後、それをアプリケーションに通知する仕組みが良く分からなかった。コールバック関数のメソッドは、まったくUIとは無関係のスレッドで呼ばれるので、そのスレッドからフォームなどの UI を操作できない。そのスレッドから UI のスレッドを呼ぶために、Windows タイマー(System.Windows.Forms.Timer)を使ったらまったく発火しない。ダメなんだ。サーバーベースタイマー(System.Timers.Timer)を使うと発火はするが、そのスレッドからUIをいじるとどうもうまくいかない。UIの描画がちゃんとされない。Repaint がちゃんとされないような感じ。SynchronizingObject プロパティーにフォームを設定しても、たんに、同期をとるようにするだけであり、UI スレッドかどうかの判断まではしてくれないのだろう。たぶん?


こういうときは Invoke メソッドを使うとうまくいく。
http://www.atmarkit.co.jp/fdotnet/dotnettips/312ctrlinvoke/ctrlinvoke.html
http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=44288&forum=7


なお、HttpWebRequest に限らず一般にこういう状況では、自分で別スレッドを作ってしまって、その中で同期APIを呼ぶ、という方法もある。ただ、unibon は昔から、自分で別スレッドを作るというのはあまりやりたくない。非同期にしたいだけなのにスレッドにしてしまうと、スレッド以外で非同期にしたいと思ったときに大幅な改造をしなければならなくなるからだ。