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

Posted

技术标签:

【中文标题】如何在漫长的循环中使用服务器端变量更新客户端?【英文标题】:How do I update the client with server side variables during a lengthy loop? 【发布时间】:2012-11-23 08:54:22 【问题描述】:

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

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

我正在使用 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;
            
        


【问题讨论】:

如果您不太关心对旧版浏览器的支持,这听起来是个使用web api 和web sockets 的好地方。 考虑使用 ajax 调用中的 complete: 来启动计时器。现在,您的计时器可以在 ajax 调用完成之前到期,从而导致新的 ajax 调用在前一个调用完成之前开始。 【参考方案1】:

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

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

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

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

您需要做的基本上是进行第一次调用以开始漫长的过程,然后反复进行第二次调用,使用第二种方法来获取长期调用的状态。

AJAX

这是您对漫长过程的主要调用。如果需要,您需要将数据传递给该方法。注意有一个processName。这应该是一个随机字符串,以确保您仅获得此进程的状态。其他用户将有不同的随机 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

然后瞧……

所以回顾一下..

    创建 2 个 Web 方法 - 一个用于执行长流程,一个用于返回其进度 为这些 Web 方法创建 2 个单独的调用。第一个是给主要工作人员的,第二个是重复 xx 秒的,给一个会给出进度的人 以您认为合适的方式向用户输出您的消息...

如果有一些错别字,请不要拍我:)

【讨论】:

您的技术基本相同,只是您调用了两个 Web 方法。我已经进行了这些调整以匹配您上面的代码,但不幸的是它不起作用。标签根本不会更新。 并非如此。您正在尝试计算漫长过程的同一按钮上执行 PostBack。因此,您实际上不会“看到”计数的变化。在我的我有两种方法。一个通过ajax启动进程(在你的情况下计数)。第二种方法就像您的 GetCount。这是您用来输出到客户端的第二种方法。我的没有人执行任何类型的回发。您需要将它们分成 2 个不同的进程,一个用于运行,一个用于检查。你不能同时做两个。 我发布的代码是我为每天都会使用的生产环境编写的代码的一部分。效果很好:) 我认为不同之处在于我的服务器端进程处于循环中,您没有在示例中指定这一点,但我猜您正在逐步进行而不是循环? 您也不需要为此使用会话。在我的示例中,发回的数据首先被添加到具有客户端设置的 ID 的字典中。这样,多个客户端可以运行相同的代码而不会发生任何数据冲突。为此使用 Sessions 可能会真正减慢多个用户的速度。【参考方案2】:

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

【讨论】:

谢谢凯文 我错过了你的关键点,我应该让服务器在单独的 Web 服务中工作。谢谢。【参考方案3】:

可能是最新且最有趣的实现方式(如果您没有将其隔离在 Web 服务器进程之外),这将是使用 Signal R 集线器将消息中继回客户端。在这里查看 git hub https://github.com/SignalR/SignalR

【讨论】:

看起来很有趣,值得探索,但我正在寻找一个更具体到我展示的代码的解决方案。【参考方案4】:

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

应该在 IIS 之外存在的进程上安排长时间运行的任务。没有理由让您的网络服务器陷入与提供网页无关的东西。您可以通过在 Web 应用程序代码中生成一个进程或创建一个在后台持续运行的服务并检查一些公共资源(如数据库)的作业来做到这一点。

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

【讨论】:

“您可以通过在您的 Web 应用程序代码中生成一个进程来做到这一点”——您能举个例子或提供更多详细信息吗? 这个问题应该给你一个好的开始:***.com/questions/6817777/… “创建一个在后台持续运行的服务,并检查一些公共资源(如数据库)的作业”。这与达伦的例子是我所追求的。谢谢。

以上是关于如何在漫长的循环中使用服务器端变量更新客户端?的主要内容,如果未能解决你的问题,请参考以下文章

javascript中如何获取session里存储的list的值

服务器端 C# Websocket,如何在监听取消令牌时运行循环?

如何跟踪服务器端更新客户端?

动态日期变量

Lua开发手记

Tomcat如何实现Comet