WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

進捗状況の表示

by WebSurfer 2015年12月28日 18:07

ASP.NET Web Forms アプリで、サーバー側にて非同期で行われている処置の進捗状況を Label コントロールや jQuery UI の Progressbar を使って表示する方法を書きます。

進捗状況の表示

ScriptManager, UpdatePanel, UpdateProgress を使えば非同期要求をかけて応答が戻ってくるまでの間、サーバーで処理中という情報をユーザーに示すことはできます。具体的な方法は MDSN ライブラリの記事UpdateProgress コントロールの概要を見てください。

しかしながらサーバー側での処置が何 % 終わったかというような進捗状況を表示することはできません。何故ならサーバー側での処理が終わるまでサーバーからは何の応答も返ってこないからです。

サーバー側での処理の進捗状況をブラウザに表示する場合、サーバー側での処理は別スレッドで行うようにし、ブラウザからタイマーを使って定期的に AJAX で進捗状況をサーバーに問合せ、サーバーからその応答をもらって表示するということは可能です。

時間のかかる処理をこのような方法で行うのが適切か、Web ファーム / ガーデン対応やワーカープロセスのリサイクル対応はどうするかという話はちょっと置いておいて、こうすれば何とかできるという具体的な例を以下に書きます。

(Thread を使うのは "don't even think about it" という意見もありますので注意してください。詳しくはこの記事の下の方の 2015/12/29 追記を見てください)

まず「時間のかかる処理」ですが、処置を行うだけでは当然ダメで、何らかの方法で進捗状況を取得できる必要があります。(進捗状況が取得できないような処理もあるかと思いますが、そういう場合はお手上げです)

具体例は以下のコードを見て下さい。DoTask メソッドで処置を行うと同時に進捗(以下の例では 0 ~ 100 まで)を記録し、Progress プロパティで進捗状況を取得できるようにしています。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Threading;

// 下の Task は自作のカスタムクラス
// System.Threading.Tasks 名前空間の Task クラスではない

public class Task
{
    public int Progress { get; set; }
    
    public Task()
    {
        this.Progress = 0;
    }
    
    public void DoTask()
    {
        for (int i = 0; i < 100; i++)
        {
            Thread.Sleep(100);
            this.Progress = i + 1;
        }
    }    
}

ASP.NET Web Forms アプリのページで上のコードの処理を別スレッドで実行します。Timer を使って 1 秒毎に非同期要求をかけ、Progress プロパティで進捗(上のコード例では 0 ~ 100 まで)を取得し、Label コントロールと jQuery UI の Progressbar に表示するようにしています。

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Threading" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">
  // Task は App_Code に定義したカスタムクラス
  // その中の DoTask メソッドで処理を行う
  // 進捗状況は Progress プロパティで取得する
  private Task task = null;

  protected void Page_Load(object sender, EventArgs e)
  {
    // Session["Task"] が null でなければ処理中
    object obj = Session["Task"];
    if (obj != null)
    {
      task = (Task)obj;
    }
  }

  protected void Button1_Click(object sender, EventArgs e)
  {
    // 処理中の場合は何もしないで return
    if (task != null)
    {
      return;
    }

    // Task クラスを初期化して Session に保持
    task = new Task();
    Session["Task"] = task;
        
    // 別スレッドで処理を実行
    Thread newThread = new Thread(task.DoTask);
    newThread.Start();
  }

  // Timer.Interval プロパティに設定したインターバル
  // (この例では 1 秒)で非同期呼び出しがかかって
  // Page_Load メソッドのあとこのメソッドが実行される
  protected void Timer1_Tick(object sender, EventArgs e)
  {
    if (task != null)
    {
      if (task.Progress == 100)
      {
        Label1.Text = "完了";
                
        // ラベルに表示しない場合もこれだけは必要
        Session.Remove("Task");
      }
      else
      {
        Label1.Text = task.Progress.ToString() + "%";
      }

      // Progress Bar に表示する進捗データを設定
      if (ScriptManager.GetCurrent(this).IsInAsyncPostBack)
      {
        ScriptManager.GetCurrent(this).
          RegisterDataItem(this, task.Progress.ToString());
      }
    }
    else
    {
      Label1.Text = "処置は行われていません";
    }        
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title></title>
  <script src="/jquery.js" type="text/javascript"></script>
  <script src="/jquery-ui.js" type="text/javascript"></script>
  <link href="/jquery-ui.css" rel="stylesheet" type="text/css" />
  <style type="text/css">
    .ui-progressbar {
        position: relative;
    }

    .progress-label {
        position: absolute;
        left: 50%;
        top: 4px;
        font-weight: bold;
        text-shadow: 1px 1px 0 #fff;
    }
  </style>
  <script type="text/javascript">
  //<![CDATA[

    function pageLoad(sender, args) {
      if (args.get_isPartialLoad() === false) {
        var manager = 
          Sys.WebForms.PageRequestManager.getInstance();
        manager.add_endRequest(OnEndRequest);
      }
    }

    // Timer で非同期要求がかかり応答が戻ってくるたびに以下の
    // メソッドが実行される
    function OnEndRequest(sender, args) {
      // サーバー側の Timer1_Tick メソッドで設定した進捗
      // データをクライアント側で取得
      var progress = args.get_dataItems()["__Page"];

      // 進捗データを Progress Bar に表示する
      if (progress) {
        $("#progressbar").
          progressbar("value", Number(progress));
      }
    }

    // jQuery UI のデモのコードを借用
    $(function () {
      var progressbar = $("#progressbar"),
          progressLabel = $(".progress-label");

      progressbar.progressbar({
        value: false,
        change: function () {
          progressLabel.
            text(progressbar.progressbar("value") + "%");
        },
        complete: function () {
          progressLabel.text("Complete!");
        }
      });
    });
  //]]>
  </script>
</head>
<body>
  <form id="form1" runat="server">
  <asp:ScriptManager ID="ScriptManager1" runat="server">
  </asp:ScriptManager>

  <%--これが ProgressBar になる
  jQuery UI のデモのコードを借用--%>
  <div id="progressbar">
    <div class="progress-label">Loading...</div>
  </div>

  <asp:Button ID="Button1" runat="server" 
    Text="Start Task" OnClick="Button1_Click" />

  <asp:UpdatePanel ID="UpdatePanel1" runat="server">
    <ContentTemplate>
      <asp:Label ID="Label1" runat="server" />
    </ContentTemplate>
    <Triggers>
      <asp:AsyncPostBackTrigger ControlID="Timer1" 
        EventName="Tick" />
    </Triggers>
  </asp:UpdatePanel>

  <asp:Timer ID="Timer1" runat="server" 
    Interval="1000" OnTick="Timer1_Tick">
  </asp:Timer>
  </form>
</body>
</html>

上記のコードを Chrome から呼んで、[Start Task]ボタンクリックで処置を開始し、その進捗状況を表示したのが上の画像です。

-------- 2015/12/29 追記 --------

MSDN Blog の記事 Performing Asynchronous Work, or Tasks, in ASP.NET Applications によると、Thread を使うのは、その記事を書いた Microsoft の開発者よりはるかにスマートに実装できるのでなければ "don't even think about it" だそうです。(汗)

理由はその記事の FAQ の 4 番目に書いてありますが、以下に概略を書いておきます(誤訳はあるかも)。

  1. CLR ThreadPool を使用するのに比べて非常にコストが高い。
  2. 自分で作った Thread に I/O 要求が残ってないか終了前にチェックしなければならない。
  3. システムのパフォーマンスを保つには実行されている Thread の数が適切でなければならないが、自分で Thread を作るのであればパフォーマンスを保つのは自分の責任になる。

暇なサイトならともかく、要求が多いサイトの場合はやはり問題になりそうです。

Tags:

AJAX

ModalPopup でプログレス表示

by WebSurfer 2011年5月29日 17:43

AJAX による部分ページ更新のプログレスを表示するには、普通、UpdateProgress コントロールを利用しますが、代わりに ModalPopup を使った例を書いてみました。

ModalPopup はクライアントのコードで表示/非表示にできますので、リクエストの開始と完了のイベントがクライアント側で取得できれば、そのイベントハンドラでコントロールできます。

イベントは PageRequestManager クライアントオブジェクトに用意されている beginRequest と endRequest を使っています。PageRequestManager クラスの詳しい説明は MSDN ライブラリ を参照してください。

以下のコードは、実際に動かして試せるよう 実験室 にアップしましたので、興味のある方は試してみてください。

<%@ Page Language="C#" %>
<%@ Register Assembly="AjaxControlToolkit" 
    Namespace="AjaxControlToolkit" 
    TagPrefix="asp" %>

<!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);
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>WebSurfer's Page - 実験室</title>
  <script type="text/javascript">
  //<![CDATA[
      var manager;

      function pageLoad(sender, args) {
          if (args.get_isPartialLoad() === false) {
              manager =
            Sys.WebForms.PageRequestManager.getInstance();
              manager.add_beginRequest(OnBeginRequest);
              manager.add_endRequest(OnEndRequest);
          }
      }

      function OnBeginRequest(sender, args) {
          var modalPopupBehavior =
          $find('programmaticModalPopupBehavior');
          modalPopupBehavior.show();
      }

      function OnEndRequest(sender, args) {
          var modalPopupBehavior =
          $find('programmaticModalPopupBehavior');
          modalPopupBehavior.hide();
      }

      function AbortPostBack() {
          if (manager.get_isInAsyncPostBack()) {
              manager.abortPostBack();
          }
      }
  //]]>
  </script>

  <style type="text/css">
    /*Modal Popup*/
    .modalBackground {
      background-color: Gray;
      filter: alpha(opacity=70);
      opacity: 0.7;
    }

    #UpdatePanel1 { 
      width: 200px; 
      height: 200px; 
      border: gray 2px solid;
      position: relative;
      float: left; 
      margin-left: 10px; 
      margin-top: 10px;
    }

    .progress {
      height: 50px;
      width: 400px;
      background-color: White;
    }
  </style>
</head>
<body>
  <form id="form1" runat="server">
    <asp:ToolkitScriptManager 
      ID="ToolkitScriptManager1" 
      runat="server">
    </asp:ToolkitScriptManager>
    <asp:UpdatePanel ID="UpdatePanel1"  
      runat="server">
      <ContentTemplate>
        UpdatePanel
        <hr />            
        <%=DateTime.Now.ToString() %> <br />
        <asp:Button ID="Button1" 
          runat="server" 
          Text="Refresh Panel" 
          OnClick="Button_Click" />    
        <br /><br />
        [Refresh Panel]ボタンをクリックすると、
        5 秒後にこのパネル内が更新されます。
        その間 ModalPopup が表示されます。
      </ContentTemplate>
    </asp:UpdatePanel>

    <asp:Button ID="DummyButton" 
      runat="server" 
      style="display: none;" />

    <asp:Panel ID="Panel1" 
      runat="server" 
      CssClass="progress">
      <asp:Image ID="Image1" 
        runat="server" 
        ImageUrl="~/img/grid-loading.gif" />
      非同期ポストバックで更新中です・・・
      <input type="button" 
        value="中止" 
        onclick="AbortPostBack()" />
    </asp:Panel>
    <asp:ModalPopupExtender ID="ModalPopupExtender1" 
      runat="server" 
      TargetControlID="DummyButton"
      BehaviorID="programmaticModalPopupBehavior"
      PopupControlID="Panel1" 
      BackgroundCssClass="modalBackground" />
  </form>
</body>
</html>

Tags: , ,

AJAX

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年3月  >>
252627282912
3456789
10111213141516
17181920212223
24252627282930
31123456

View posts in large calendar