ASP.NET AJAX : 非同期によるWebサービス呼び出しの落とし穴

詳細は、後半に記述しますが、本件のテーマは、「非同期通信における120秒の壁」になります。

先日の投稿、「Silverlight 1.0 + ASP.NET AJAX : 試行錯誤を減らすための参考情報 :ScriptingJsonSerializationSectionクラスのMaxJsonLengthプロパティのサイズ設定をお忘れなく」において、時間の問題については無視していましたが、現在開発しているアプリケーションでプロファイルをとってみました。

あまり精度は高くないのですが、次のようなJavaScriptをSilverlightアプリケーションに組み込んで、Webサービスを呼び出してからのおおよその経過時間を表示するというものです。

 

 var inCallingWS; // Webサービスを呼び出す直前にtrueにし、戻ってきたらfalseにする
var idTimeout, start, lastTick;

function ShowElapsedTime()
{
    var interval = 500;
    if (start == null) {
        start = new Date();
    }
    if (inCallingWS == false) {
        clearTimeout(idTimeout);
        start = null;
        return ;
    }
    lastTick = new Date();
    deltaT = parseInt((lastTick.getTime() - start.getTime()) / 1000); 

    textElapse.text = deltaT.toString();  // ここで、textElapseはTextBlockのオブジェクトだとします
    idTimeout = setTimeout("ShowElapsedTime()", interval);
}

function invokeMyWebService()
{
    inCallingWS = true;
    AgSrv01.MyClass.getUIStringByKey(document.getElementById('TextKey').value, OnSuccess, OnError);
    start = null;
    ShowElapsedTime();
}

functon OnSuccess(arg)
{
    inCalingWS = false;
    // 成功したときの処理あれこれ
}

function OnError(error)
{
    alert('Error');
    inCalingWS = false;
    // 失敗したときの処理あれこれ
    var stackTrace = error.get_stackTrace();
    var message = error.get_message();
    var statusCode = error.get_statusCode();
    var exceptionType = error.get_exceptionType();
    var timedout = error.get_timedOut();
   
    // Display the error.
    var errMessage = 
        "Stack Trace: " +  stackTrace + "\r\n" +
        "Service Error: " + message + "\r\n" +
        "Status Code: " + statusCode + "\r\n" +
        "Exception Type: " + exceptionType + "\r\n" +
        "Timedout: " + timedout;
    /// 
}

 

この状態で、問題となっているWebサービスの呼び出しがエラーとなる場合を確認してみると、大体112-120くらいの数字が表示されています。ここでWebサービスのProxyのタイムアウト時間は既定値の0となっています。いったい何が起きているでしょうか。
Generated Proxy Classes timeout Propertyのドキュメントには、この0の意味がこう書かれています。

The network executor interprets a value of zero to mean that it is the responsibility of the underlying network stack to wait for the time out interval.

TCP/IPネットワークの知識がある方ならお気づきでしょうが、これはTCP通信でのタイムアウトに依存するわけで、Windowsの場合、「Windows XP および Windows Server 2003 における TCP 通信でのパケット再転送について」を読むと、SRTT (Smoothed Round Trip Time)の上限値が、120秒に変更されている、と書かれています。

そんなわけで、非同期通信において、約2分近くの無通信状態が続けば、タイムアウトになって、上記のコードにおけるOnError関数内でStatus Codeを参照すると500という数字が設定されるようです。HTTP通信での意味合い的には「内部サーバーエラー」という形でしかわからないのですが、サーバ側にトラブルがなければ、呼び出されたメソッドの実行はWebサービス側で継続しています。つまりクライアントとサーバの間の通信だけが切れているだけにすぎず、本当にサーバのエラーかどうかはわかりません。

非同期通信ならではの落とし穴として、TCP/IPのタイムアウトに注意しましょう。タイムアウトになった場合は、自動的に呼び出しは失敗に終わるわけで、クライアント側はサーバの状況がわかりません。現実的には、そもそも2分近く待たすような処理にならないようにしなければならないですね。

ちなみに、上記の例において、WebサービスのProxyのタイムアウトは、次のように設定できます。

    AgSrv01.MyClass.set_timeout(30000); // 30秒にした場合

これを仮に、

    AgSrv01.MyClass.set_timeout(300000); // 300秒にした場合
設定しても、TCP/IPのタイムアウトが優先されます。

この制約はASP.NET AJAXによるものではありませんが、いろいろと試行錯誤するなかでの、かなり特殊な場合に参考になればと思います。