0

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

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

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

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

谢谢。

默认.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>

默认.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

4 回答 4

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

瞧..

所以回顾一下..

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

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

于 2012-12-05T14:12:15.420 回答
1

Typical web application frameworks are not well suited for this kind of thing. That is to say this isn't just a drawback of ASP.Net you'll run into this in PHP, RoR and I suspect many other frameworks in the future.

Long running tasks should be pawned off on processes that exist outside of IIS. There's no reason to bog down your web server with something that isn't about serving web pages. You can do this by either spawning a process from within your web application code or create a service that is constantly running in the background and checking some common resource (like a database) for jobs.

In either case the common resource would serve as a progress indicator. The external process would continuously update some value say, PercentComplete as the task moves along. Meanwhile your web application would need some kind of method for checking this value and returning it to the client. An AJAX web method would probably give you the best results.

于 2012-12-05T13:59:59.897 回答
1

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

于 2012-12-05T14:01:00.623 回答
0

Probably the most up to date and fun way of implementing (if you're not hiving it off outside the web server process) this would be to use Signal R hubs to relay back messages to the client. Check it out on git hub here https://github.com/SignalR/SignalR

于 2012-12-05T14:00:41.323 回答