none
ICallbackEventHandler利用時のエラーについて RRS feed

  • 質問

  • Microsoft Visual Web Developer 2005 Express Editionで、開発しています。

    (ASP.NET 2.0、vb.net)

    System.Web.UI.ICallbackEventHandler.RaiseCallbackEvent

    System.Web.UI.ICallbackEventHandler.GetCallbackResult

    を利用しています。

    1度のイベント発生時に2回以上呼び出しをすると、

     

    !__pendingCallbacks.async

     

    でエラーとなります。どうやらasp.NET がクライアントの処理文を生成時に問題があるようです。

    以下にクライアントに生成された文を記述します。ここで、変数iがvar付で生成されていないため、その他の関数とぶつかっているようです。

    何か、対処する方法は無いでしょうか?

    ----------------------------------------- 以下生成されたソース

    function WebForm_CallbackComplete() {
        for (i = 0; i < __pendingCallbacks.length; i++) {
            callbackObject = __pendingCallbacks;
            if (callbackObject && callbackObject.xmlRequest && (callbackObject.xmlRequest.readyState == 4)) {
                WebForm_ExecuteCallback(callbackObject);
                if (!__pendingCallbacks.async) {
                    __synchronousCallBackIndex = -1;
                }
                __pendingCallbacks = null;
                var callbackFrameID = "__CALLBACKFRAME" + i;
                var xmlRequestFrame = document.getElementById(callbackFrameID);
                if (xmlRequestFrame) {
                    xmlRequestFrame.parentNode.removeChild(xmlRequestFrame);
                }
            }
        }
    }

    2007年2月6日 4:33

回答

  • さて、時間が取れたので、お金さんのサンプルコードを実際に動作させてみました。
    結果として、コードを見て想定していた通りの動作をしており、これまでの投稿に書いてあることをもう1度読み直してください、というところなのですが、せっかくなので適当に追記してみます。書いてあることは、これ以前の投稿とまったくおなじで、ちょっと書き方をかえてみるだけです・・・。
     

    >> 「非同期リクエストが解決しないうちに、複数回の非同期リクエストの呼び出しが発生している状態になっていますね」

    > ICallbackEventHandler の動作を理解していないようですね。何度も呼び出されることはありません。

    サンプルコードでの Test2 が呼び出されるまでの、具体的なコールスタックでいうと、

    - root
    -- Test()
    --- CallServer ……1回目のリクエストの登録
    ---- WebForm_DoCallback 
    -- XMLHttpRequest.readystatechange
    --- WebForm_CallbackComplete
    ---- ユー***ールバックの呼び出し ……1回目のリクエストのコールバックを開始
    ----- ReceiveServerData()
    ------ Test1()
    ------- CallServer() ……2回目のリクエストの登録
    -------- WebForm_DoCallback
    ---- ユー***ールバックの完了 ……1回目のリクエストのコールバックを終了
    -- XMLHttpRequest.readystatechange
    --- WebForm_CallbackComplete
    ---- ユー***ールバックの呼び出し  ……2回目のリクエストのコールバックを開始
    ----- ReceiveServerData()
    ------ Test2()

    といった形になっています。

    問題になっているのは「2回目のリクエストの登録」の部分が「1回目のリクエストのコールバックを開始」してから「1回目のリクエストのコールバックを終了」までの間に存在してしまっているということです。この瞬間に、1回目のリクエストの完了処理が解決しおわらないうちに2回目のリクエストを追加しようとしているため、ASP.NET の不具合にひっかかってしまいます。これを

    - root
    -- Test()
    --- CallServer ……1回目のリクエストの登録
    ---- WebForm_DoCallback 
    -- XMLHttpRequest.readystatechange
    --- WebForm_CallbackComplete
    ---- ユー***ールバックの呼び出し ……1回目のリクエストのコールバックを開始
    ----- ReceiveServerData()
    ------ Test1()
    -------- WebForm_DoCallback
    ---- ユー***ールバックの完了 ……1回目のリクエストのコールバックを終了
    -- CallServer() ……2回目のリクエストの登録
    -- XMLHttpRequest.readystatechange
    --- WebForm_CallbackComplete
    ---- ユー***ールバックの呼び出し  ……2回目のリクエストのコールバックを開始
    ----- ReceiveServerData()
    ------ Test2()

     
    というかんじで、「1回目のリクエストのコールバックを終了」より後ろに位置するようにすることで、ASP.NET の問題を回避することができます。
    これを容易に実施できるのが window.setTimeout です。window.setTimeout は、JavaScript のコールスタックが root に戻るまで、割り込みを発生させないので、delay=0 で window.setTimeout を実行することで、ちょうど上記の状態にすることができます。
     
    任意のパラメータを与えた window.setTimeout はコードが複雑になるので、とりあえず引数を定数化してしまいますが、
        function Test1(RValue) {
            var Arg = "B";
            var Context = "Test2";
    
            CountA += 1;
            document.getElementById("dvTest1").innerHTML = RValue + " : " + CountA;
    
            //CallServer(Arg, Context);
            window.setTimeout("CallServer('B', 'Test2')", 0);
        }
    
     
     
    と、setTimeout の呼び出しに修正することで、スクリプトエラーが出ない状態になることが確認できます。実際の運用では CallServer の引数を定数ではないものにしなければならないでしょうから、引数を文字列ではなくて無名関数などにして解決すると良いと思います。
     
    > 実際の動きを見ていないのにわかるのでしょか?
     
    コードをみて動作を想像・把握するためには、前知識や記述されたコードの読みやすさ、コードの量といったものに左右されますが、お金さんのサンプルコードは、問題点を十分に単純化されたものであるため、実際に動作させなくても結果を容易に予測できるものです。(実際に予測通りの動作をしていました)
     
    > ただ、ブラウザの下に、「!」で、WebForm_CallbackComplete() のスクリプトエラーが発生しているだけです。
    > これを気にしなければ特に処理的には問題なく動作します。
     
    XMLHttpRequest からの通知は UI と切り離されているので、メジャーなブラウザはコッソリエラーを通知するだけですが、エラーが発生した以降のコードは使い終わったコールバックオブジェクトや、擬似 XMLHttpRequest 用の IFRAME の破棄処理なので、この部分が動作しないとブラウザの消費しているメモリが時間の経過と共に増えていく可能性があります。(・・・デバッガで追う限りはなさそうですが)
     
    なにより、エラーのある状態を放置しておくと、利用者であるユーザが気がついたときに不安になりますので、消しておくのが良いと思います。
    2007年2月16日 3:18
  • ご回答ありがとう御座います。

    一連の流れを説明していただけると、問題点がわかります。

    これを見ると、複数回発生しているのが原因ではないですね。

    WebForm_DoCallback と、WebForm_CallbackComplete の処理が衝突して
    発生しているということですね。
    ※実際、この処理の流れがどのようにしてわかったのか記載がないため、
     これだけが衝突しているか不明ですが。
     又、対策も、この部分の回避のため、動作するのか判断がつきませんが。

    大まかな理解としては、

       WebForm_DoCallback
       XMLHttpRequest.readystatechange
       WebForm_CallbackComplete
        ※これだけかどうかは不明

    上記、ASP.NET が生成している部分の変数領域が、リクエスト毎に保持
    されていないことにより、WebForm_CallbackComplete でエラーが発生
    しているようですね。
    これら関数が、同時に実行されないようにするための対処として、

    window.setTimeout

    を代替的に使えば対処出来るということですね。
     ※window.setTimeout の処理で、CallServer 部分を見落としていました。
        自分で作った関数内での衝突を問題視していると思っていたもので。

    ASP.NET2.0 より、System.Web.UI.ICallbackEventHandler が使えるように
    なり、だいぶ助かっているのですが、この部分がネックになっていました。


        function Test1(RValue) {
            var Arg = "B";
            var Context = "Test2";

            CountA += 1;
            document.getElementById("dvTest1").innerHTML = RValue + " : " + CountA;

            //CallServer(Arg, Context);
            //window.setTimeout("CallServer('B', 'Test2')", 0);
            window.setTimeout("CallServer('" + Arg + "', '" + Context + "')", 0);

        }


    上記のようにすることで、エラーもなく、共通に利用出来るようになりました。
    ありがとうございます。

    2007年2月16日 5:15

すべての返信

  • 02-06-2007 4:33 午前 UTC   ?

    昼に投稿したはずだが・・・・・。

    ちなみにこの投稿は、02-06-2007 2:08 午後 UTC 

    2007年2月6日 5:08
  • どうやら、フレームワークのバグのようです。

    ATLS モジュールなどでも発生しているようです。

    以下に、マイクロソフトとのやり取りがあります。

    http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=101974

    よくわかりませんでした。

    今後、AJAX を利用する場合、

    エラー:'__pendingCallbacksIdea.async'はNull またはオブジェクトではありません。

    が発生する可能性があります。

     

     

     

     

     

     

     

    2007年2月9日 5:15
  • 記載されている Feedback の問題であれば、事前のリクエストが完全に完了してから、次のリクエストに移るように実行順序を入れ替えてやる必要があります。
    (同様の問題はいくつかあって http://d.hatena.ne.jp/ladybug/20061211#p2 とかも同じ原因)
     
    (これは、変数 i がローカルスコープになっていないとかいう問題ではないです)
     
    簡単な対応方法は、MSDN Forum(英語) や、ご自身で記載されている feedback にあるように、complete/fail イベントのハンドラから、request 操作を行うメソッドを window.setTimeout で、タイムアウト時間を 0 で呼び出すことです。
     
    本格的にやるならば、JavaScript はシングルスレッドで同期オブジェクト類が存在しないので、タスク管理システムを自前で用意し、Callback の呼び出しをタスクとして登録するかんじになると思います。
     
     
     
    # ちなみに、UTC は UTC であって、日本時間ではないですよ?
     
    2007年2月14日 8:00
  • 御回答ありがとうございます。

    まず根本的なことから言いますと、フレームワークが生成しているソースは変更できません。
    (WebForm_CallbackComplete() は、フレームワークが生成しているものです。)

    ※出来るならやり方を教えてください。

    従って、あなたが提案しているやり方は出来ません。

    > Just add a line of javascipt
    > if (__pendingCallbacksIdea.async != null)
    > {
    > }


    これも出来ません。※これは私の回答ではありません。どうやって組み込むのでしょう?
    又、

    > request 操作を行うメソッドを window.setTimeout で、タイムアウト時間を 0 で呼び出すことです。

    これもよくわかりません。どのタイミングでの話でしょう?

    > 本格的にやるならば、JavaScript はシングルスレッドで同期オブジェクト類が存在しないので、
    > タスク管理システムを自前で用意し、Callback の呼び出しをタスクとして登録するかんじになると思います。

    System.Web.UI.ICallbackEventHandler は、使えないということでしょうか?
    スレッドの考えは、ここでは関係ないと思われますが。


     http://d.hatena.ne.jp/ladybug/20061211#p2

    これも、System.Web.UI.ICallbackEventHandler の利用時の問題と関係ないと思われますが。

    > # ちなみに、UTC は UTC であって、日本時間ではないですよ?

    なるほど。なぜUTC を使ってるのでしょうね?

     

     

    2007年2月14日 10:52
  •  お金 さんからの引用

    まず根本的なことから言いますと、フレームワークが生成しているソースは変更できません。
    (WebForm_CallbackComplete() は、フレームワークが生成しているものです。)

    ※出来るならやり方を教えてください。

    従って、あなたが提案しているやり方は出来ません。

     
    うーん、私の提案しているやり方とは、前回の記述に書いた
     
    ・window.setTimeout を 0 で呼び出す
    ・タスク管理を自分でしっかりやる
     
    のどちらかだと思いますが、どちらもフレームワークの生成するメソッドを修正するような必要はありません。
    JavaScript の実行順序が、
     
    1. GetCallbackResult() によるコールバックの完了をうけて WebForm_XXX が呼び出される
    2. WebForm_XXX から、ユー***ールバックハンドラの呼び出しが実行される
    3. ユー***ールバックハンドラから WebForm_XXX へ処理が戻る
     
    という順序で実行されますが、このうち、3. の完了を待たずに、新しい非同期リクエストを発行してしまうと問題が発生するので、それを避けるように 2. の部分のコードを調整することで、この問題を回避しなければならない、というものです。
     
    お金さんが2回以上の呼び出しを実施されている部分のコードが、どのようになっているのかわかりませんが、たとえば前述の日記の例であれば、
    このようなコードになって
    function request_progress()
    {
       // ここに GetCallbackEventReference() を callback = "display_progress" で設定
    }
    
    function display_progress(result, context)
    {
      $("progress").value = "現在 " + result + " %完了";
    
      if (result < 100)
      {
        request_progress();
      }
    }
    このようなコードになっていますが、ここで、request_progress() を Button.Click などから呼び出すと、
     
    1. GetCallbackResult() によるコールバックの完了をうけて WebForm_XXX が呼び出される
    2. WebForm_XXX から、ユー***ールバックハンドラ display_progress の呼び出しが実行される
         display_progress から request_progress が呼び出され、新しい非同期リクエストが発生する
    3. ユー***ールバックハンドラから WebForm_XXX へ処理が戻る
     
    となり、2. の部分で新しい非同期リクエストが発生してしまい、問題が発生します。これを避けるために
    function display_progress(result, context)
    {
      $("progress").value = "現在 " + result + " %完了";
    
      if (result < 100)
      {
        window.setTimeout(request_progress, 0);
      }
    }
     などと、timeout を 0 に設定した window.setTimeout() を使用することで、
     
    1. GetCallbackResult() によるコールバックの完了をうけて WebForm_XXX が呼び出される
    2. WebForm_XXX から、ユー***ールバックハンドラ display_progress の呼び出しが実行される
    3. ユー***ールバックハンドラから WebForm_XXX へ処理が戻る
    4. timer 処理によって request_progress が呼び出され新しい非同期リクエストが発生する
     
    という順序になり、問題を回避することができます。
     
    -- 以下は脱線ですが、
     
    > なぜUTC を使ってるのでしょうね?
     
    日本時間 (JST) にしてほしいという要望は多く寄せられているようですけど、MSDN Forum そのものは、ワールドワイドに展開されていて、他の国の人々もおなじように現地時刻ではなく UTC で参照されているようです。要望が多ければ、そのうちフォーラム単位で表示時刻を設定できるような機能が導入されるのではないかと思います。
    (日本語のところからは日本語フォーラムだけがリストされていますが、違う言語のフォーラムも覗けます)
     
    ただ、日本語のフォーラムは日本人がメインユーザですけど、英語のフォーラムなんかはユーザの国はまちまちです。米国なんて国内だけでも時差が3時間とかあります。そういった事情を考えると、Web の UI における時刻表記をローカライズするのは色々と大変な作業です。
    xbox.com なんかは、JavaScript を利用して「ブラウザに設定されているタイムゾーン」で描画したりしていますが、ローカル時刻表示しちゃうと、米国などで「3時頃の投稿で」みたいな話が通じなくなる可能性があるなど、掲示板のようなコミュニケーションシステム固有の問題点もありますので、「日本だけ日本時間にさくっとならんのか?」ってわけにはいかないんでしょうね。
     
     
2007年2月14日 18:36
  • 何度もご返答ありがとうございます。

    > ・window.setTimeout を 0 で呼び出す
    > ・タスク管理を自分でしっかりやる

    どうやら、問題の趣旨が異がうようですね。
    まず、何度も呼び出されたために発生している問題ではありません。
    マイクロソフトとのやり取りで使用しているサンプルをご覧になったのでしょうか?
    そのため、回答も以下の構文を付加するとなっているのだと思うのですが?

    > Just add a line of javascipt
    > if (__pendingCallbacks.async != null)
    > {
    > }


    論点がずれているようなので、もう一度記載しますが、
    ICallbackEventHandler を利用時の問題点です。
    ICallbackEventHandler は、マイクロソフトが提供しているものです。
     System.Web.UI.ICallbackEventHandler.RaiseCallbackEvent
     System.Web.UI.ICallbackEventHandler.GetCallbackResult
    を使い、AJAXを行うやりかたです。
    従って、「タスク管理を自分でしっかりやる」も当てはまりません。

    長くなりますが、ここで、ASP.net + VB.net によるサンプルをつけます。



    ''-------------------------------------------------------------VB.NET 部分(始め) CallCheck.aspx.vb
    Partial Class CallCheck
        Inherits System.Web.UI.Page
        Implements System.Web.UI.ICallbackEventHandler

        Private _Return As String

        '''<summary>Callbackによる処理</summary>
        '''<param name="eventArgument">クライアントからの受信データ</param>
        '''<remarks>クライアントからの送信時の処理</remarks>
        Protected Sub RaiseCallbackEvent(ByVal eventArgument As String) Implements System.Web.UI.ICallbackEventHandler.RaiseCallbackEvent
            Select Case eventArgument
                Case "A"
                    _Return = "TestA"
                Case "B"
                    _Return = "TestB"
            End Select

     

        End Sub

        Protected Function GetCallbackResult() As String Implements System.Web.UI.ICallbackEventHandler.GetCallbackResult
            Return _Return
        End Function

        Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
            Dim strCallbackScript As String
            Dim strReference As String

            strReference = Page.ClientScript.GetCallbackEventReference(Me, "strArg", "ReceiveServerData", "strContext")
            strCallbackScript = "function CallServer(strArg, strContext)" & "{" & strReference & "; }"
            Page.ClientScript.RegisterClientScriptBlock(Me.GetType(), "CallServer", strCallbackScript, True)

        End Sub
    End Class
    ''-------------------------------------------------------------VB.NET 部分(終わり)


    -------------------------------------------------------------ASP 部分(始め)  CallCheck.aspx
    <%@ Page Language="VB" AutoEventWireup="false" CodeFile="CallCheck.aspx.vb" Inherits="CallCheck" %>

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>無題のページ</title>
    <script language="javascript">
    <!--
        var CheckFlg = "Start";
        var CountA;

        function ReceiveServerData(RValue, Context) {
            switch (Context) {
                case "Test1":
                    if (CheckFlg == "Start") {
                        CheckFlg = "";
                        Test1(RValue);
                    }
                    break;
                case "Test2":
                    Test2(RValue);
                    break;
            }

        }

        function Test() {
            var Arg = "A";
            var Context = "Test1";
            CountA = 1;
            document.getElementById("dvTest").innerHTML = Context;
            CallServer(Arg, Context);
         
        }

        function Test1(RValue) {
            var Arg = "B";
            var Context = "Test2";

            CountA += 1;
            document.getElementById("dvTest1").innerHTML = RValue + " : " + CountA;
            CallServer(Arg, Context);
         
        }

        function Test2(RValue) {
            CountA += 1;
            document.getElementById("dvTest2").innerHTML = RValue + " : " + CountA;
        }

     //
    //-->
    </script>
    </head>
    <body>
        <form id="form1" runat="server">
        <div id="dvTest">
        </div>
        <div id="dvTest1">
        </div>
        <div id="dvTest2">
        </div>
        </form>
    </body>
    <script language="javascript">
    <!--
        Test();

     //
    //-->
    </script>
    </html>

    ''-------------------------------------------------------------ASP 部分(終わり)


     

    2007年2月15日 4:46
  •  お金 さんからの引用

    マイクロソフトとのやり取りで使用しているサンプルをご覧になったのでしょうか?
    そのため、回答も以下の構文を付加するとなっているのだと思うのですが?

    > Just add a line of javascipt
    > if (__pendingCallbacks.async != null)
    > {
    > }

    ごめんなさい、みてません。Microsoft からの回答は、

    Thanks for the feedback. We will consider fixing this in a future release. As a workaround, use window.setTimeout with a zero delay to delay the creation of new callbacks until the current one is finished processing.

    The Web Platform and Tools Team

    投稿者: Microsoft、入力日: 2005/11/15、入力時刻: 15:34

    なので、いい加減に翻訳すると

    フィードバックありがとうございます。この問題については次期リリースで修正が行われる見通しです。回避策として、window.setTimeout を待ち時間 0 で呼び出すことで、新しいコールバックの生成を処理の終了後に移動してください。

    となっていますよね。

     お金 さんからの引用

    論点がずれているようなので、もう一度記載しますが、
    ICallbackEventHandler を利用時の問題点です。
    ICallbackEventHandler は、マイクロソフトが提供しているものです。
     System.Web.UI.ICallbackEventHandler.RaiseCallbackEvent
     System.Web.UI.ICallbackEventHandler.GetCallbackResult
    を使い、AJAXを行うやりかたです。

    はい、最初からその話しかしていません。

     お金 さんからの引用

    従って、「タスク管理を自分でしっかりやる」も当てはまりません。

    タスク管理をしなければならないのは ASP.NET 側ではなくて JavaScript 側のコールバック処理のリクエストです。とても面倒なので window.setTimeout を呼び出すのが簡単です。

     お金 さんからの引用

    まず、何度も呼び出されたために発生している問題ではありません。

    とかかれていますが、添付されているサンプルは複数回呼び出しになっていますね。

        function ReceiveServerData(RValue, Context) {
            switch (Context) {
                case "Test1":
                    if (CheckFlg == "Start") {
                        CheckFlg = "";
                        Test1(RValue);
                    }
                    break;
                case "Test2":
                    Test2(RValue);
                    break;
            }

    ここの、Test1 と Test2 を window.setTimeout() で遅延させ、リクエスト完了前のコールバック呼び出しを防げば解決すると思いますが、いかがでしょうか?

     

    2007年2月15日 9:09
  • ご回答ありがとうございます。

    > とかかれていますが、添付されているサンプルは複数回呼び出しになっていますね。

    意味が違います。これは、自分で指定した順序で呼び出しています。

    > 非同期クライアントコールバックのコールバック関数からクライアントコールバック
    > を呼び出すと、コールバック関数が呼ばれ続ける

    上記が、あなたが参照しているページの問題点であったと思いますが。
    今回のは、コールバック関数が何度も呼び出しを続けるわけではありません。
    従って、この呼び出し方とは意味が違います。
    同時に、Test1 と Test2 が動くわけではありません。
    このため、window.setTimeout を何を基準に設定するのか意味がわかりません。

    上記の話だと WebForm_CallbackComplete() 内のiが他の関数と同時に使われる
    ことのほうがあるように思われるのですが?
    これに関しては、こちらではテストできません。

    そもそも、ためしてみてからご回答を頂けるとありがたいのですが。
    サンプルで既に現象は発生します。これにどのように手を加えるか
    お教えできれば何度もやり取りせずすむのでありがたいのですが。


     

     

    2007年2月15日 10:23
  •  お金 さんからの引用

    > とかかれていますが、添付されているサンプルは複数回呼び出しになっていますね。

    意味が違います。これは、自分で指定した順序で呼び出しています。

    すいません、書き方がわるかったようです。

    「非同期リクエストが解決しないうちに、複数回の非同期リクエストの呼び出しが発生している状態になっていますね」

    と言いたかったのです。

     お金 さんからの引用

    > 非同期クライアントコールバックのコールバック関数からクライアントコールバック
    > を呼び出すと、コールバック関数が呼ばれ続ける

    上記が、あなたが参照しているページの問題点であったと思いますが。
    今回のは、コールバック関数が何度も呼び出しを続けるわけではありません。

    表層的な問題点は、他にもいくつか発生します。私は、お金さんの発生している問題の状況が、何度も呼び出されることであるとは思っていませんし、そうなるとも言ってません。スクリプトエラーがでるとか、コールバックの呼び出し回数がおかしくなるとかいった、表層的な症状ではなくて、
     
    「非同期リクエストが完結しないうちに非同期リクエストを追加することによる不具合」
     

    という根本的な事柄に焦点をあててお話しているはずです。ですので、ASP.NET2.0 の現状のクライアントコールバックの JavaScript では、どんな書き方であろうとも、どんな順序であろうとも、上記の条件を満たせば、数パターンある問題のどれかが発生するのです。

     お金 さんからの引用

    このため、window.setTimeout を何を基準に設定するのか意味がわかりません。

    何度か書いているのですが、上記の条件を満たさなくするようにするために window.setTimeout を使用します。

    具体的な手順の入れ替えについては 6:36 PM UTC の投稿をご覧ください。

     お金 さんからの引用

    そもそも、ためしてみてからご回答を頂けるとありがたいのですが。

    すいません、時間と環境が許せばそうしたいところですが、現状そういった状態にないので申し訳ないですが、できません。

    提示されているサンプルコードは、みただけで問題を発生することは容易に想像がつききますし、十分なものだと思います。

    2007年2月15日 10:40
  • 何度も回答ありがとう御座います。

    > 「非同期リクエストが解決しないうちに、複数回の非同期リクエストの呼び出しが発生している状態になっていますね」

    サンプルを実行していないようですね。なっていません。
    ICallbackEventHandler の動作を理解していないようですね。何度も呼び出されることはありません。


    > 「非同期リクエストが完結しないうちに非同期リクエストを追加することによる不具合」

    そもそも今回のものがこれが原因とどうしていえるのでしょう?
    完結していない理由は何でしょう?

    > 具体的な手順の入れ替えについては 6:36 PM UTC の投稿をご覧ください。

    同様にいれても同じエラーは発生します。
    そもそも、上記では、非同期リクエストが完結の判断ができません。
    又、既に完結しているのに入れるのは無意味です。


    > みただけで問題を発生することは容易に想像がつききますし

    実際の動きを見ていないのにわかるのでしょか?
    実際は、処理は正常に完結しています。
    ただ、ブラウザの下に、「!」で、WebForm_CallbackComplete() のスクリプトエラーが発生しているだけです。
    これを気にしなければ特に処理的には問題なく動作します。
    何度も同じ処理が動作することはありません。

     そもそも、あなたのあげられている現象は、WebForm_CallbackComplete() のエラーとは関係
    ないのではないでしょうか?
    あくまで、呼び出しを行っている関数内での話でしょう。
    WebForm_CallbackComplete()  のタイミングを考えるに、あなたのあげられている現象以前
    の話だと思われますが。
    従って、回避策としては意味をなしません。

     


     

    2007年2月15日 12:04
  • さて、時間が取れたので、お金さんのサンプルコードを実際に動作させてみました。
    結果として、コードを見て想定していた通りの動作をしており、これまでの投稿に書いてあることをもう1度読み直してください、というところなのですが、せっかくなので適当に追記してみます。書いてあることは、これ以前の投稿とまったくおなじで、ちょっと書き方をかえてみるだけです・・・。
     

    >> 「非同期リクエストが解決しないうちに、複数回の非同期リクエストの呼び出しが発生している状態になっていますね」

    > ICallbackEventHandler の動作を理解していないようですね。何度も呼び出されることはありません。

    サンプルコードでの Test2 が呼び出されるまでの、具体的なコールスタックでいうと、

    - root
    -- Test()
    --- CallServer ……1回目のリクエストの登録
    ---- WebForm_DoCallback 
    -- XMLHttpRequest.readystatechange
    --- WebForm_CallbackComplete
    ---- ユー***ールバックの呼び出し ……1回目のリクエストのコールバックを開始
    ----- ReceiveServerData()
    ------ Test1()
    ------- CallServer() ……2回目のリクエストの登録
    -------- WebForm_DoCallback
    ---- ユー***ールバックの完了 ……1回目のリクエストのコールバックを終了
    -- XMLHttpRequest.readystatechange
    --- WebForm_CallbackComplete
    ---- ユー***ールバックの呼び出し  ……2回目のリクエストのコールバックを開始
    ----- ReceiveServerData()
    ------ Test2()

    といった形になっています。

    問題になっているのは「2回目のリクエストの登録」の部分が「1回目のリクエストのコールバックを開始」してから「1回目のリクエストのコールバックを終了」までの間に存在してしまっているということです。この瞬間に、1回目のリクエストの完了処理が解決しおわらないうちに2回目のリクエストを追加しようとしているため、ASP.NET の不具合にひっかかってしまいます。これを

    - root
    -- Test()
    --- CallServer ……1回目のリクエストの登録
    ---- WebForm_DoCallback 
    -- XMLHttpRequest.readystatechange
    --- WebForm_CallbackComplete
    ---- ユー***ールバックの呼び出し ……1回目のリクエストのコールバックを開始
    ----- ReceiveServerData()
    ------ Test1()
    -------- WebForm_DoCallback
    ---- ユー***ールバックの完了 ……1回目のリクエストのコールバックを終了
    -- CallServer() ……2回目のリクエストの登録
    -- XMLHttpRequest.readystatechange
    --- WebForm_CallbackComplete
    ---- ユー***ールバックの呼び出し  ……2回目のリクエストのコールバックを開始
    ----- ReceiveServerData()
    ------ Test2()

     
    というかんじで、「1回目のリクエストのコールバックを終了」より後ろに位置するようにすることで、ASP.NET の問題を回避することができます。
    これを容易に実施できるのが window.setTimeout です。window.setTimeout は、JavaScript のコールスタックが root に戻るまで、割り込みを発生させないので、delay=0 で window.setTimeout を実行することで、ちょうど上記の状態にすることができます。
     
    任意のパラメータを与えた window.setTimeout はコードが複雑になるので、とりあえず引数を定数化してしまいますが、
        function Test1(RValue) {
            var Arg = "B";
            var Context = "Test2";
    
            CountA += 1;
            document.getElementById("dvTest1").innerHTML = RValue + " : " + CountA;
    
            //CallServer(Arg, Context);
            window.setTimeout("CallServer('B', 'Test2')", 0);
        }
    
     
     
    と、setTimeout の呼び出しに修正することで、スクリプトエラーが出ない状態になることが確認できます。実際の運用では CallServer の引数を定数ではないものにしなければならないでしょうから、引数を文字列ではなくて無名関数などにして解決すると良いと思います。
     
    > 実際の動きを見ていないのにわかるのでしょか?
     
    コードをみて動作を想像・把握するためには、前知識や記述されたコードの読みやすさ、コードの量といったものに左右されますが、お金さんのサンプルコードは、問題点を十分に単純化されたものであるため、実際に動作させなくても結果を容易に予測できるものです。(実際に予測通りの動作をしていました)
     
    > ただ、ブラウザの下に、「!」で、WebForm_CallbackComplete() のスクリプトエラーが発生しているだけです。
    > これを気にしなければ特に処理的には問題なく動作します。
     
    XMLHttpRequest からの通知は UI と切り離されているので、メジャーなブラウザはコッソリエラーを通知するだけですが、エラーが発生した以降のコードは使い終わったコールバックオブジェクトや、擬似 XMLHttpRequest 用の IFRAME の破棄処理なので、この部分が動作しないとブラウザの消費しているメモリが時間の経過と共に増えていく可能性があります。(・・・デバッガで追う限りはなさそうですが)
     
    なにより、エラーのある状態を放置しておくと、利用者であるユーザが気がついたときに不安になりますので、消しておくのが良いと思います。
    2007年2月16日 3:18
  • ご回答ありがとう御座います。

    一連の流れを説明していただけると、問題点がわかります。

    これを見ると、複数回発生しているのが原因ではないですね。

    WebForm_DoCallback と、WebForm_CallbackComplete の処理が衝突して
    発生しているということですね。
    ※実際、この処理の流れがどのようにしてわかったのか記載がないため、
     これだけが衝突しているか不明ですが。
     又、対策も、この部分の回避のため、動作するのか判断がつきませんが。

    大まかな理解としては、

       WebForm_DoCallback
       XMLHttpRequest.readystatechange
       WebForm_CallbackComplete
        ※これだけかどうかは不明

    上記、ASP.NET が生成している部分の変数領域が、リクエスト毎に保持
    されていないことにより、WebForm_CallbackComplete でエラーが発生
    しているようですね。
    これら関数が、同時に実行されないようにするための対処として、

    window.setTimeout

    を代替的に使えば対処出来るということですね。
     ※window.setTimeout の処理で、CallServer 部分を見落としていました。
        自分で作った関数内での衝突を問題視していると思っていたもので。

    ASP.NET2.0 より、System.Web.UI.ICallbackEventHandler が使えるように
    なり、だいぶ助かっているのですが、この部分がネックになっていました。


        function Test1(RValue) {
            var Arg = "B";
            var Context = "Test2";

            CountA += 1;
            document.getElementById("dvTest1").innerHTML = RValue + " : " + CountA;

            //CallServer(Arg, Context);
            //window.setTimeout("CallServer('B', 'Test2')", 0);
            window.setTimeout("CallServer('" + Arg + "', '" + Context + "')", 0);

        }


    上記のようにすることで、エラーもなく、共通に利用出来るようになりました。
    ありがとうございます。

    2007年2月16日 5:15
  • すみません。一部訂正です。

    >        window.setTimeout("CallServer('" + Arg + "', '" + Context + "')", 0);

    これだと、シングルコーテイションが文字列内で使えなくなります。

        window.setTimeout("CallServer(" + '\"' + strArg + '\"' +  ", " + '\"' + strContext + '\"' + ")", 0);

    一応上記でシングルコーテイションも使えます。
    この辺は、各コーディングされている方独自で色々あると思いますので、とりあえずこれでクローズします。

    2007年2月23日 13:29