Silverlight + JavaScriptなネタですが、ちょっとしたトランザクション処理と見立てると面白いと思うので、あえてPASSJブログへ、Webリソース読み込みというトランザクション処理、というものを書いてみます。別途MSDNブログにも書きたいと思っています。
このところSilverlightに関わることが増えて、実際に動かすためのコードを書いていることが多いです。その先には技術啓蒙があるものの、「百聞は一見にしかず」で動いているものをお見せして、その裏側のからくりをきちんと説明できる状況に自分自身を置くことを心がけてます。いわゆる借りてきたサンプルを動作させるのではなく、要件に合致したものを組み上げていくということが中心になっているので、実際のコードを書いていることが多くなるわけです。
今のところはSilverlight 1.0による技術的なチャレンジに向き合っていますが、頭で考えたことが現実の形になると本当に面白いものだということを、今更ながら実感しています。私が駆け出しのプログラマーだった頃の日々の喜びみたいなものをIT業界で19年を過ごし、20年目に突入したばかりのおっさんが感じているわけですから、それは本当に面白いんです。また、ものすごい多数の目に触れるものに手を入れていることもあり、ただ面白いだけではなく、ものすごい緊張との戦いであることも事実です。自分自身の興味のためのプログラミングではなく、他人に使ってもらうアプリケーション構築であるので、きちんと要求を満たす、ということは最低条件として、想定しているシナリオ上で予期せぬ例外が発生しないようにする、というのも必ず意識しています。
たとえば、次のページにあるSilverlightアプリケーションには、私が考案した安定稼動のためのロジックが入っています。
http://www.microsoft.com/japan/business/solutions/virtualization/rio.mspx
初期のバージョンは別の方が作られたものですが、インターネット上の配置において予期せぬ問題が発生し、私の調査から安定稼動のためのコードを提示して組み込んでいただき、改良を加えたものです。
この単純なアプリケーションでも、トランザクションの問題のように正常動作か例外か、を切り分けるための再帰呼び出しによる仕掛けが入っています。わかってしまえば単純なことなのですが、その単純なことが未経験者のテスト環境ではわからないことがあります。
多くの場合、リソースの読み込みはルーズに書いてもテスト環境ではうまく行きます。リソースがダウンロードできない状況をテストしていないからです。でも必ずしもHTTP GETが成功するとは限らないのです。成功しなかったら、失敗という状態にあることをアプリケーションが理解する必要があります。しかし、そのために数多くのif文を書くのは可読性においても保守性においても好ましくありません。
で、やったことは、次のようなコードを作ったことです。
var LoadedResourceCount = 0; // 読み込まれた画像の数
var ExpectedNumberOfResources = 38; // 想定している画像の総数
// 読み込み成功:XAML上は"Loaded"にマッピングされるイベントハンドラ
function OnResourceReady(sender, args) {
if (LoadedResourceCount >= 0) { // 画像読み込みエラーが起きてないことを確認して、成功数を加算する
LoadedResourceCount ++;
}
}
// 読み込み失敗:XAML上は"ImageFailed"にマッピングされるイベントハンドラ
function OnResourceFailed(sender, args)
{
LoadedResourceCount = -1; // 読み込みエラーは-1と定義する
}
function IsReadyToRunThisApp()
{
if (LoadedResourceCount == ExpectedNumberOfResources) { // すべて読み込まれたので実行してよい
return true;
}
else {
return false; // まだ読み込みが終わっていないので実行してはいけない
}
}
function IsFailedToRunThisApp()
{
if (LoadedResourceCount == -1) { // 読み込み失敗しているので、実行できない
return true;
}
else {
return false;
}
}
var idTimeout;
function WaitForAllResourceLoaded()
{
if (IsReadyToRunThisApp() == true ) {
clearTimeout(idTimeout);
return true;
}
else if (IsFailedToRunThisApp() == true ) {
clearTimeout(idTimeout);
return false;
}
}
これを準備した段階で、SilverlightのアプリケーションがXAMLをロードし終えた直後に、
idTimeout = setTimeout("WaitForAllResourceLoaded", 500);
// ..
if (WaitForAllResourceLoaded() == false) {
// 何らかのエラーメッセージを表示して、ブラウザの再読み込みを促す
return false;
}
// 以下、正常系
というコードを仕掛ければリソース読み込みが異常な場合は、最初のif文で判断が付き、トランザクション的な発想で成功か失敗かということを切り分けられるというものです。少なくとも正常系までステップが到達していれれば、Silverlightのランタイム上にはダウンロード済みのリソース(この場合、画像を想定)が見えているので、画像が表示できない、という問題は発生しないことになります。
Webアプリケーションなので、詳細レベルでは、HTTP GETのエラーうんぬんてなことを考えることもできますが、HTTPを利用しているランタイム上のフレームワークが、
・HTTP GETの成功 -> ファイルが見つかりダウンロードできた -> Loadedイベントの呼び出し
・HTTP GETの失敗 -> ファイルが見つからない -> ImageFailedイベントの呼び出し
という形になっていることを想像すれば(実際に、私自身も想像して考案したので)、フレームワーク上のイベントを処理すれば細かいことは気にしなくてよい、ということになります。現実に詳細レベルでは、フレームワーク内で非同期のダウンロードが実行されているので、一見簡単な処理で捕捉できないようにも思えますが、フレームワークを信じれば、単純な問題に帰着できるわけです。結局、リソースが準備できなければ、それ以降の処理をあきらめるしかないので、トランザクション的には失敗なわけです。反対に準備できればそれ以降の処理を継続できるので、トランザクション的には成功。
「ネットワークタイムアウトはどうなるの?」という疑問を感じた方へ、TCP/IPは無通信が一定時間継続するとタイムアウトして終了します。これは結局、HTTPレベルでも失敗なわけなので、問題ないと思います。Ajaxの通信でも正常終了時、タイムアウト(またはエラー発生時)のイベント呼び出しが分かれてますよね。これもWebサービスからの応答をトランザクションに見立てて成功・失敗で切り分けようと単純化しているものだと思います。
そんなわけで、Webリソースの読み込みというトランザクション処理を考えるのもありかもしれませんね。で、「待ってダメならあきらめよう」、Windowsだと「(約2分)待ってダメならあきらめよう」みたいな感じで。
これで、ブラウザ上のリッチクライアントでもリソースのダウンロードの成否がはっきりするので、安心して利用できるのではないでしょうか。皆様もお試しあれ。
本内容はデータベースとは無関係ですが、非同期通信におけるトランザクションとしては他にも応用できるかもしれません。何か面白いアイディアを見つけたら教えていただけると幸いです。