如何在冗长的循环中使用服务器端变量更新客户端?

时间:2012-12-05 13:53:30

标签: c# javascript asp.net asp.net-4.5

我正在尝试在冗长的服务器端进程上放置一个Web界面,该进程应该在进程运行时向客户端发送常规进度\统计报告。我怎样才能做到这一点?

这是我到目前为止所尝试的内容。只要循环正在处理,webmethod中的会话就为null。完成循环并再次按下开始按钮后,它就可以获取会话值并填充标签。如何在进程运行时将此更新发送给客户端?

我正在使用VS2012和ASP.NET 4.5。

编辑:更具体地说,当服务器忙于循环时会出现问题。如果我取消循环并且只是尝试定期从服务器中提取变量值,那么就没有问题了。将该变量放在循环中并尝试定期获取它,您将看到问题所在,我发布的代码应该澄清问题,如果您运行它。

感谢。

Default.aspx的

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="ClientProgressTest.Default" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
    <script type="text/javascript">
        function GetCount() {

        $.ajax({
            type: "POST",
            url: "Default.aspx/GetCount",
            contentType: "application/json; charset=utf-8",
            data: {},
            dataType: "json",
            success: function (data) {
                lblCount.innerHTML = data.d;
            },
            error: function (result) {
                alert(result.status + ' ' + result.statusText);
            }
        });

        setTimeout(GetCount, 5000);
    }
</script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <label id="lblCount"></label>
        <br />
        <br />
        <asp:Button ID="btnGetCount" runat="server" Text="Get Count" OnClientClick="GetCount();" OnClick="btnGetCount_Click" />
    </div>
    </form>
</body>
</html>

Default.aspx.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace ClientProgressTest
{
    public partial class Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {

        }

        [WebMethod(EnableSession = true)]
        public static string GetCount()
        {
            string count = null;

            if (HttpContext.Current.Session["count"] != null)
            {
                count = HttpContext.Current.Session["count"].ToString();
            }

            return count;
        }

        protected void btnGetCount_Click(object sender, EventArgs e)
        {
            Session["count"] = null;

            for (int i = 0; i < 10000000; i++)
            {
                Session["count"] = i;
            }
        }
}
}

4 个答案:

答案 0 :(得分:1)

典型的Web应用程序框架不太适合这种情况。也就是说,这不仅仅是ASP.Net的一个缺点,你将在PHP中遇到这个问题,RoR和我怀疑未来会有很多其他框架。

应该在IIS之外的进程上进行长时间运行的任务。没有理由使用与服务网页无关的东西来阻止您的Web服务器。您可以通过从Web应用程序代码中生成进程或创建在后台持续运行的服务并检查作业的某些公共资源(如数据库)来实现此目的。

在任何一种情况下,公共资源都可以作为进度指标。随着任务的进行,外部流程会不断更新某些值,PercentComplete。同时,您的Web应用程序需要某种方法来检查此值并将其返回给客户端。一个AJAX Web方法可能会给你最好的结果。

答案 1 :(得分:1)

创建一个将返回流程当前进度的Web服务。在客户端脚本中设置一个计时器,在触发时,从Web服务获取当前进度,然后使用新值动态更新客户端显示元素。

答案 2 :(得分:1)

我在这里对这个问题做了一个冗长的回答:How to update a status label inside ajax request

为了让其他人能够找到相同的解决方案,我也会在这里粘贴答案:

=============================================== ===================================

此示例使用JQuery作为AJAX,代码隐藏在vb.net中。

基本上你需要做的就是让你的第一次调用开始漫长的过程,然后重复进行第二次调用,使用第二种方法来获得长的过程的状态。

AJAX

这是您对漫长过程的主要要求。如果需要,您需要将数据传递给方法。请注意,有一个processName。这应该是一个随机的字符串,以确保您只获得此进程的状态。其他用户将拥有不同的随机进程名称,因此您不会感到困惑。

<强> JAVASCRIPT

    var processName = function GenerateProcessName() {

        var str = "";
        var alhpabet = "abcdefghijklmnopqrstuvwxyz";
        for (i = 1; i < 20; i++) {
            str += alhpabet.charAt(Math.floor(Math.random() * alhpabet.length + 1));
        }
        return str;
    }


    function StartMainProcess(){
    $j.ajax({
            type: "POST",
            url: "/MyWebservice.asmx/MyMainWorker",
            data: "{'processName' : '" + processName + "','someOtherData':'fooBar'}",
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            success: function (msg) {
                if (msg.d) {                        
                    // do a final timerPoll - process checker will clear everything else
                    TimerPoll();
                }
            }
        });
        TimerPoll();
     }

您的第二个AJAX调用将是另一种获取进度的方法。这将通过计时器方法每隔XXX次调用一次。

这是TimerPoll功能;在这种情况下将每3秒触发一次

<强> JAVASCRIPT

function TimerPoll() {
        timer = setTimeout("GetProgress()", 3000)
    }

最后,GetProgress()函数可以获得进度。我们必须传入上面使用的相同processName,才能获得此用户调用的过程

<强> JAVASCRIPT

function GetProgress() {
        // make the ajax call
        $j.ajax({
            type: "POST",
            url: "/MyWebService.asmx/MyMainWorkerProgress",
            data: "{'processName' : '" + processName + "'}",
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            success: function (msg) {

                // Evaulate result..
                var process = msg.d

                if (process.processComplete) {
                    // destroy the timer to stop polling for progress
                    clearTimeout(timer);

            // Do your final message to the user here.                      

                } else {

                   // show the messages you have to the user.
                   // these will be in msg.d.messages

                    // poll timer for another trip
                    TimerPoll();
                }
        });

    }

现在,在后端,您将有几个与AJAX通信的Web方法。您还需要一个共享/静态对象来保存所有进度信息,以及您想要传递给用户的任何内容。

在我的例子中,我创建了一个类,它的属性已经填充,并在每次调用MyMainWorkerProcess时传回。这看起来有点像这样。

<强> VB.NET

    Public Class ProcessData
        Public Property processComplete As Boolean
        Public Property messages As List(Of String) = New List(Of String)
    End Class

我还有一个使用此类的共享属性,它看起来像......(这个共享属性能够由多个用户保存多个进程 - 因此是字典。字典的键将是进程名称。所有进度数据将在ProcessData类中

Private Shared processProgress As Dictionary(Of String, ProcessData) = New Dictionary(Of String, ProcessData)

我的主要工作人员功能看起来有点像这样。请注意,我们首先确保没有其他具有相同

的processProgress

<强> VB.NET

<WebMethod()> _
<ScriptMethod(ResponseFormat:=ResponseFormat.Json)> _
Public Function MyMainWorker(ByVal processName as string, ByVal SomeOtherData as string) as Boolean

        '' Create progress object - GUI outputs process to user
        '' If the same process name already exists - destroy it
        If (FileMaker.processProgress.ContainsKey(processName)) Then
            FileMaker.processProgress.Remove(processName)
        End If

        '' now create a new process
        dim processD as ProcessData = new ProcessData() with {.processComplete = false}


        '' Start doing your long process.

        '' While it's running and after whatever steps you choose you can add messages into the processData which will be output to the user when they call for the updates
         processD.messages.Add("I just started the process")

         processD.messages.Add("I just did step 1 of 20")

         processD.messages.Add("I just did step 2 of 20 etc etc")

         '' Once done, return true so that the AJAX call to this method knows we're done..
        return true

End Function

现在剩下的就是调用progress方法了。所有这一切都是返回字典processData,它具有我们之前设置的相同processName ..

<强> VB.NET

<WebMethod()> _
    <ScriptMethod(ResponseFormat:=ResponseFormat.Json)> _
    Public Function MyMainWorkerProgress(ByVal processName As String) As ProcessData

        Dim serializer As New JavaScriptSerializer()

        If (FileMaker.processProgress.ContainsKey(processName)) Then
            Return processProgress.Item(processName)
        Else
            Return New ProcessData()
        End If

    End Function

瞧..

所以回顾一下..

  1. 创建2个Web方法 - 一个用于执行漫长的过程,另一个用于返回其进度
  2. 创建对这些Web方法的2个单独调用。第一个是主工作人员,第二个是重复xx秒,一个是进步的
  3. 将您的消息输出给用户但是您觉得合适......
  4. 如果有一些拼写错误,请不要开枪:)

答案 3 :(得分:0)

可能是最新和最有趣的实施方式(如果你不是在Web服务器进程之外进行操作),这将是使用Signal R集线器将消息中继到客户端。在git hub https://github.com/SignalR/SignalR

上查看