none
非同期ポストバック中のキャンセルとその後のポストバックについて RRS feed

  • 質問

  •     function CancelPostBack()
        {
            var prm = Sys.WebForms.PageRequestManager.getInstance();
            if (prm.get_isInAsyncPostBack()) {
                //非同期ポストバック中のときは、キャンセルする
                prm.abortPostBack();
            }
        }
    

    ScriptManagerとUpdateProgress、UpdatePanelを使い非同期送信を行う用にしたASP.NETページに対して

    UpdateProgressが表示される時に、「キャンセル」ボタンを表示させ、キャンセルボタンクリック時に上記命令を実行させています。

    チャンと上記命令までステップが進む事までは確認できましたが、上記命令後に「キャンセル」以外のボタンなどをクリックすると

    数秒間ブラウザー(IE11)がロード中のまま動きません。Webサーバー側にもリクエストが数秒間届かない状態です。

    このブラウザーの動き(フリーズみないなロード中状態)を無くすまたは、短くする方法はありますか?

    もしかして、上記命令の実行方法が間違えているのでしょうか?

    御協力をお願い致します。

    2017年1月23日 11:58

回答

  • おそらくなのですが、セッション変数を利用していませんか?
    ASP.NETではセッション変数を利用しているとリクエストが直列化されます。
    (セッション変数の読み書きの順番を保証して、安全に利用するための仕組みです)

    ですので、もしセッション変数を利用しているのであれば、非同期ポストバックをクライアント側でキャンセルしたとしても、サーバサイドで前のリクエストが終了するまでは次のリクエストが処理されないのではないかと思います。




    きよくらならみ

    • 回答としてマーク やきGO 2017年2月3日 13:08
    2017年1月31日 4:13
  • Kiyokura さんのレスを見て思い当たることがありましたので、以下に書いておきます。

    質問者さんのアップされたコードでは Session は使っていない(コードで明示的に Session["Data"] = xxx のようなことはしていない)ようですが、自分でコードを書いて使った覚えがないのに使われるケースがあります。

    それは、テンプレートを使って ASP.NET Web Forms アプリケーションを作って、Global.asax に中身が空の Session_Start ハンドラが自動生成される場合です。詳しくは以下の記事を見てください。

    Session_Start ハンドラの影響
    http://surferonwww.info/BlogEngine/post/2014/07/26/session-state-management-and-session-start-event-handler-in-global-asax.aspx

    Session_Start ハンドラがあると、コードで明示的に Session["Data"] = xxx のように Session を使ってなくても、サーバーは Session State 情報用のストレージを割り当てて Session Cookie を発行します。

    そうなると、セッション状態モジュールによるロックメカニズムで、要求の処理中はセッションデータのリーダー/ライターにロックがかかるので、次の要求を受けても、現在の要求の処理が終わるまで、次の要求は待たされます。

    先に私がアップしたサンプルコードで試してみましたが、Global.asax に中身が空の Session_Start ハンドラを置くと、要求を受けた順番に処理が終わって応答が返されるまで次の要求は待たされるという動きになります。

    これと同じことが起こっているのでは?

    解決策は:

    (1) Session を使ってなければ Global.asax の Session_Start ハンドラをを削除するだけで済むはずです。

    (2) 他のページで Session を使っているが、問題のページでは使ってなければ EnableSessionState を False に設定する。

    (3) 問題のページでも Session を使っているが、読み取りのみの場合は EnableSessionState を ReadOnly に設定する。

    ・・・でしょうか。問題のページで Session に書き込んでいる場合は何ともならないと思います。

    • 回答としてマーク やきGO 2017年2月3日 13:08
    2017年2月1日 3:39

すべての返信

  • 以下の記事に書いてあるようにやってみてはいかが?

    ModalPopup でプログレス表示
    http://surferonwww.info/BlogEngine/post/2011/05/29/Show-progress-in-ModalPopup.aspx

    2017年1月23日 13:05
  • フォーラム オペレーターの栗下 望です。
    やきGO さん、 MSDN フォーラムをご活用いただきありがとうございます。

    ご質問いただいた内容については、その後いかがでしょうか?
    SurferOnWww さんから情報をいただいていますので確認をいただければと思います。

    情報が参考になりましたら[回答としてマーク]をお願いいたします。


    MSDN/TechNet Community Support 栗下 望

    2017年1月27日 2:36
    モデレータ
  • 頂いた内容を元にチャレンジしましたが解決しておりません。

    下記のサンプルで言いますと、Button1をクリックし、表示される「Button1の処理をキャンセル」をクリック。

    その後、Button2のボタンをクリックするとButton2の結果が返ってこないのです。

    Button1の処理はキャンセルしているので、すぐにButton2の結果を返して欲しいのですが返ってきません。

    下記ソースをどのように修正すれば解決できるのでしょうか?

    .NET Framework 4.5 を使用しています。

    WebForm1.aspx

    <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="IAA.WebForm1" %>
    
    <!DOCTYPE html>
    
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <title></title>
    </head>
    <body>
        <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server" AsyncPostBackErrorMessage="通信失敗" AsyncPostBackTimeout="0"></asp:ScriptManager>
        <script type="text/javascript" language="javascript">
        //<![CDATA[
        function CancelPostBack()
        {
            var prm = Sys.WebForms.PageRequestManager.getInstance();
            
            if (prm.get_isInAsyncPostBack()) {
                prm.abortPostBack();
            }
        }
    
        Sys.WebForms.PageRequestManager.getInstance().add_beginRequest(BeginRequestHandler);
        function BeginRequestHandler(sender, args) {
            PanelView('UpdateProgress1', true);
            PanelView('UpdatePanel1', false);
        }
    
        Sys.WebForms.PageRequestManager.getInstance().add_endRequest(EndRequestHandler)
        function EndRequestHandler(sender, args) {
            PanelView('UpdateProgress1', false);
            PanelView('UpdatePanel1', true);
        }
    
        function PanelView(elem, view) {
            var Panel = $get(elem);
            if (view == true) {
                Panel.style.display = "block";
                Panel.style.visibility = "visible";
            } else {
                Panel.style.display = "none";
                Panel.style.visibility = "hidden";
            }
        }
        //]]>
        </script>
            <div>
            <asp:Button ID="Button1" runat="server" Text="Button1" OnClick="Button1_Click" />
            <asp:Button ID="Button2" runat="server" Text="Button2" OnClick="Button2_Click" />
            <asp:Panel ID="Panel1" runat="server">Button1で非表示/Button2で表示</asp:Panel>
            <asp:UpdateProgress ID="UpdateProgress1" runat="server">
                <ProgressTemplate>Button1の処理中<input type="button" id="CancelRefresh" onclick="CancelPostBack()"  value="Button1の処理をキャンセル"></ProgressTemplate>
            </asp:UpdateProgress>
            <asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional">
                <ContentTemplate>Button1の処理結果</ContentTemplate>
                <Triggers>
                    <asp:AsyncPostBackTrigger ControlID="Button1" EventName="Click"></asp:AsyncPostBackTrigger>
                </Triggers>
            </asp:UpdatePanel>
        </div>
        </form>
    </body>
    </html>
    
    WebForm1.aspx.cs
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    
    namespace IAA
    {
        public partial class WebForm1 : System.Web.UI.Page
        {
            protected void Page_Load(object sender, EventArgs e)
            {
                this.Panel1.Visible = false;
            }
    
            protected void Button1_Click(object sender, EventArgs e)
            {
                this.Panel1.Visible = false;
                Thread.Sleep(1000000);
            }
    
            protected void Button2_Click(object sender, EventArgs e)
            {
                this.Panel1.Visible = true;
            }
        }
    }

    お助けください。

    2017年1月27日 6:31
  • 前のスレッドの話(非同期ポストバックがかかったら UpdatePanel を非表示にし、応答が戻ってきたら再度表示する)の続きですか?

    前の話からさらに Button2 を追加して何かしたいということのようですが、何をしたいのでしょう? 全体のシナリオを含めて具体的に何がしたいのかを書いてください。
    • 編集済み SurferOnWww 2017年1月27日 10:38 誤字訂正
    2017年1月27日 9:28
  • お世話になります。

    UpdatePanel の表示/非表示の命令は無視して頂いたかまいません。

    ブラウザーで通信の状態を確認したら、上記サンプルで言うButton2 クリック時にButton1のリクエストからやり直しまたは

    Button1のレスポンス待ちから再開している用に見えました。

    私がやりたい事は、上記例で言うと「Button2 のみクリック」した時と「Button1クリック後にキャンセルし、その後、Button2 を

    クリック」した時のButton2 のクリックに対するレスポンスをほぼ同じ速度にしたいと言うのが具体的な内容となります。

    わかっている事は、「Button2 のみクリック」の場合には、すぐにWebサーバー側にリクエストが届くが、「Button1クリック後に

    キャンセルし、その後、Button2 をクリック」した場合には、すぐにWebサーバー側にリクエストが届きません。

    おそらく、すぐにWebサーバー側にリクエストが届かない事が原因で同じ速度で処理ができないのだと考えておりますが・・・

    なぜ、キャンセルするとButton2 のクリックがWebサーバー側に直ぐに届かなくなるのかの理由とその解決方法がわからない

    のです。

    お助けください。

    2017年1月31日 2:49
  • > 「Button1クリック後にキャンセルし、その後、Button2 をクリック」した場合には、
    > すぐにWebサーバー側にリクエストが届きません。

    普通に作ればそういうことはないはずです。

    Button1 クリックで非同期ポストバックがかかると、サーバー側では PreRender イベントの前あたりまでは同期ポストバックと全く同じ動きをし、UpdatePanel の中身のみの応答を返します。

    ブラウザのスクリプトで abortPostBack() を実行しても、サーバー側での実行が中断されるわけではなく、応答は帰ってきます。ブラウザ側でその応答が処理されないだけです。

    Button1 クリックで非同期ポストバックをかけ、応答が返ってくる前に abortPostBack() を実行し、Button2 クリックで同期ポストバックをかけた場合、サーバーでは両方の要求を受け付けて処理し、応答を返します。

    ブラウザは、非同期ポストバックの応答は処置せず、同期ポストバックの応答のみ処置します。以下の画像は Fiddler で要求・応答をキャプチャしたものですが、その通りになっているのが分かりますか?

    なお、Button1 クリックで非同期ポストバックをかけ、応答が返ってくる前に Button2 クリックで同期ポストバックをかけた場合も同じ動きになります。

    質問者さんのコードでそのようにならないのは、想像ですが、Thread.Sleep(1000000); とか AsyncPostBackTimeout="0"> など、普通でないところで変な動きをしているような気がします。(気がするだけで実際に動かして検証したわけではありませんが)

    ご参考に、上に書いた、Fiddler で要求・応答をキャプチャしたときに使った検証用のコードをアップしておきます。

    <%@ Page Language="C#" %>
    
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    
    <script runat="server">
        protected void Button_Click(object sender, EventArgs e)
        {
            System.Threading.Thread.Sleep(5000);
        }
    
        protected void Button2_Click(object sender, EventArgs e)
        {
            
        }
    </script>
    
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title></title>
        <script type="text/javascript">
        //<![CDATA[
            var manager;
    
            function pageLoad(sender, args) {
                if (args.get_isPartialLoad() === false) {
                    manager = Sys.WebForms.PageRequestManager.getInstance();
                    manager.add_initializeRequest(OnInitializeRequest);
                    manager.add_endRequest(OnEndRequest);
                }
            }
    
            function OnInitializeRequest(sender, args) {
                $get("<%=UpdatePanel1.ClientID%>").style.display = "none";
            }
    
            function OnEndRequest(sender, args) {
                $get("<%=UpdatePanel1.ClientID%>").style.display = "block";
            }
    
    
            function AbortPostBack() {
                if (manager.get_isInAsyncPostBack()) {
                    manager.abortPostBack();
                }
            }
      //]]>
      </script>
    
        <style type="text/css">
        #UpdatePanel1 { 
          width: 200px; 
          height: 200px; 
          border: gray 2px solid;
          ;
          float: left; 
          margin-left: 10px; 
          margin-top: 10px;
        }
    
        #UpdateProgress1 {
          width: 400px; 
          background-color: #FFC080;
          border: gray 1px solid;
          /*bottom: 0%; 
          left: 0px; 
          ;*/
        }
    
      </style>
    
    </head>
    <body>
        <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager>
    
        <asp:Button ID="Button1" runat="server" Text="非同期" OnClick="Button_Click" />  
        <asp:Button ID="Button2" runat="server" Text="同期" OnClick="Button2_Click" />
        <br />
        <asp:UpdatePanel ID="UpdatePanel1" runat="server">
            <ContentTemplate>
                UpdatePanel
                <hr />            
                <%=DateTime.Now.ToString() %> <br />
                
                <br /><br />
                [非同期]ボタンをクリックすると、
                5 秒後にこのパネル内が更新されます。
                その間 UpdateProgress が表示されます。
            </ContentTemplate>
            <Triggers>
                <asp:AsyncPostBackTrigger ControlID="Button1" EventName="Click"></asp:AsyncPostBackTrigger>
            </Triggers>
        </asp:UpdatePanel>
    
        <asp:UpdateProgress ID="UpdateProgress1" runat="server">
            <ProgressTemplate>
                非同期ポストバックで更新中です・・・  
                <input type="button" value="Cancel" onclick="AbortPostBack()" />            
            </ProgressTemplate>
        </asp:UpdateProgress>
        </form>
    </body>
    </html>

    2017年1月31日 4:13
  • おそらくなのですが、セッション変数を利用していませんか?
    ASP.NETではセッション変数を利用しているとリクエストが直列化されます。
    (セッション変数の読み書きの順番を保証して、安全に利用するための仕組みです)

    ですので、もしセッション変数を利用しているのであれば、非同期ポストバックをクライアント側でキャンセルしたとしても、サーバサイドで前のリクエストが終了するまでは次のリクエストが処理されないのではないかと思います。




    きよくらならみ

    • 回答としてマーク やきGO 2017年2月3日 13:08
    2017年1月31日 4:13
  • Kiyokura さんのレスを見て思い当たることがありましたので、以下に書いておきます。

    質問者さんのアップされたコードでは Session は使っていない(コードで明示的に Session["Data"] = xxx のようなことはしていない)ようですが、自分でコードを書いて使った覚えがないのに使われるケースがあります。

    それは、テンプレートを使って ASP.NET Web Forms アプリケーションを作って、Global.asax に中身が空の Session_Start ハンドラが自動生成される場合です。詳しくは以下の記事を見てください。

    Session_Start ハンドラの影響
    http://surferonwww.info/BlogEngine/post/2014/07/26/session-state-management-and-session-start-event-handler-in-global-asax.aspx

    Session_Start ハンドラがあると、コードで明示的に Session["Data"] = xxx のように Session を使ってなくても、サーバーは Session State 情報用のストレージを割り当てて Session Cookie を発行します。

    そうなると、セッション状態モジュールによるロックメカニズムで、要求の処理中はセッションデータのリーダー/ライターにロックがかかるので、次の要求を受けても、現在の要求の処理が終わるまで、次の要求は待たされます。

    先に私がアップしたサンプルコードで試してみましたが、Global.asax に中身が空の Session_Start ハンドラを置くと、要求を受けた順番に処理が終わって応答が返されるまで次の要求は待たされるという動きになります。

    これと同じことが起こっているのでは?

    解決策は:

    (1) Session を使ってなければ Global.asax の Session_Start ハンドラをを削除するだけで済むはずです。

    (2) 他のページで Session を使っているが、問題のページでは使ってなければ EnableSessionState を False に設定する。

    (3) 問題のページでも Session を使っているが、読み取りのみの場合は EnableSessionState を ReadOnly に設定する。

    ・・・でしょうか。問題のページで Session に書き込んでいる場合は何ともならないと思います。

    • 回答としてマーク やきGO 2017年2月3日 13:08
    2017年2月1日 3:39
  • 解決策は:

    (1) Session を使ってなければ Global.asax の Session_Start ハンドラをを削除するだけで済むはずです。

    (2) 他のページで Session を使っているが、問題のページでは使ってなければ EnableSessionState を False に設定する。

    (3) 問題のページでも Session を使っているが、読み取りのみの場合は EnableSessionState を ReadOnly に設定する。

    ・・・でしょうか。問題のページで Session に書き込んでいる場合は何ともならないと思います。

    他のページはありませんし、Global.asaxもありませんが、このような動きとなっておりました。

    EnableSessionState = false を指定する事で問題は解決しました。

    御協力ありがとうございます。

    2017年2月3日 13:11
  • > 他のページはありませんし、Global.asaxもありませんが、このような動きとなっておりました。

    それは不思議な話ですね。

    以下のようなセッションクッキーの授受がないか、できればキャプチャツール(Fiddler がなければ IE の開発者ツールでもキャプチャ可能)でヘッダを見て結果を連絡いただけませんか。

    Set-Cookie: ASP.NET_SessionId=d4rbf5nhk1xpo0qouuciwxgy; path=/; HttpOnly

    2017年2月3日 13:47
  • こんにちは、確認しましたが ASP.NET_SessionId と言うクッキーは発行されておりませんでした。

    ですが、知らないクッキーがセットされておりました。

    Webサーバー管理者に確認を行ったところ、web.configでStateServiceを使用する設定が入っているとの事でした。

    2017年3月3日 1:41
  • > ASP.NET_SessionId と言うクッキーは発行されておりませんでした。
    > ですが、知らないクッキーがセットされておりました。

    cookie 名は web.config の設定(sessionState 要素の cookieName 属性)で変えられます。

    > web.configでStateServiceを使用する設定が入っているとの事でした。

    StateService ではなくて、StateServer ですよね。(sessionState 要素の mode 属性)

    イマイチはっきりしませんが、質問者さんの環境で Session は使っていると理解します。

    でなければ、上のような問題は起きないし、EnableSessionState = false で解決できるということもあり得ないですから。

    2017年3月3日 2:27