服务器上异步进程慢时,客户端调用asp.net AJAX最佳实践

4
我有一个执行缓慢任务的服务,当任务完成后,我想使用AJAX更新客户端并显示任务结果。在我的客户端程序中,我多次调用该任务以更新结果网格。为了更好地理解,这是一个连接测试器,它会遍历一组连接并检查其是否活动。
我已将该服务实现为WCF,并在Web客户端中添加服务引用时生成异步方法。
代码可以正常工作,但是当回调函数被触发时,屏幕会短暂锁定 - 我认为这是因为所有回调函数都依次发生,并且它们都迅速重新绘制GridView。 我不想出现这种故障 - 我希望AJAX实现能够通过回调函数从服务返回结果并部分地更新GridView。
唯一能让界面看起来好看的方法是启动异步调用的线程并使用计时器将数据更新到网格中(这些数据正在通过回调函数在单独的线程中进行更新)。
我正在做这个小项目作为学习练习,然后我打算使用MVC3进行相同的操作以了解差异。
代码片段(没有单独的线程,导致呈现回调函数时屏幕响应变慢):
//get list of connections from session
ConnectionList myConns = Session[SESSION_ID] as ConnectionList;
//pass into async service call
GetAllStatusAsync(myConns);


protected void GetAllStatusAsync(ConnectionList myConns)
{

Service1Client myClient = new WcfConnectionServiceRef.Service1Client();
myClient.AsyncWorkCompleted += new EventHandler<AsyncWorkCompletedEventArgs>(myClient_AsyncWorkCompleted);

foreach (ConnectionDetail conn in myConns.ConnectionDetail)
  {
  //this call isnt blocking, conn wont be updated until later in the callback
  myClient.AsyncWorkAsync(conn);
  }
}

//callback method from async task
void myClient_AsyncWorkCompleted(object sender, AsyncWorkCompletedEventArgs e)
{

ConnectionDetail connResult = e.Result;

//get list of connections from session
ConnectionList myConns = Session[SESSION_ID] as ConnectionList;

//update our local store
UpdateConnectionStore(connResult, myConns);

//rebind grid
BindConnectionDetailsToGrid(myConns);

}

这个问题是 - 在asp.net/AJAX中,是否有更好的方法来解决这个问题?(避免呈现锁定问题并在结果到达时部分更新网格)我不想使用像以下代码片段这样的单独客户端线程:
 // Perform processing of files async in another thread so rendering is not slowed down 
 // this is a fire and forget approach so i will never get results back unless i poll for them in timer from the main thread
ThreadPool.QueueUserWorkItem(delegate
  {
  //get list of connections from session
  ConnectionList myConns = Session[SESSION_ID] as ConnectionList;
  //pass into async service call
  GetAllStatusAsync(myConns);
  });

更新:

根据要求添加页面标记:

<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="ASForm.aspx.cs" Inherits="Web_Asp_FBMonitor.ASForm" Async="true" EnableSessionState="True" %>
<asp:Content ID="Content1" ContentPlaceHolderID="HeadContent" runat="server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

    <h2>
        ASP.NET Connection Test (Client in ASYNC, Server in ASYNC)
    </h2>

    <asp:ScriptManager ID="ScriptManager1" runat="server">
    </asp:ScriptManager>


    <p>

        <%--This update panel shows the time, updated every second--%>    
        &nbsp;<asp:UpdatePanel ID="UpdatePanel2" runat="server">
        <ContentTemplate>

            <h3> <asp:Label ID="LabelTime" runat="server" Text=""></asp:Label>  </h3>
            <asp:Timer ID="Timer1" runat="server" Interval="1000" ontick="Timer1_Tick">  </asp:Timer>

            </ContentTemplate>
    </asp:UpdatePanel>
    </p>

    <p>

        <%--This update panel shows our results grid--%>
        &nbsp;<asp:UpdatePanel ID="UpdatePanel1" runat="server">
        <ContentTemplate>

            <asp:GridView ID="GridView1" runat="server">
            </asp:GridView>

            <asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="~/Default.aspx">Client Sync Page</asp:HyperLink>

            <br />

            <asp:Button ID="ButtonUpdate" runat="server" Text="Update" 
                onclick="ButtonUpdate_Click" />
        </ContentTemplate>
        </asp:UpdatePanel>




    </p>



</asp:Content>

更新2:

我正在寻找一个简洁的客户端JS示例。收到的选项很好,非常感谢,但是客户端JS是我自己缺乏经验所苦恼的部分,因此将授予此内容的奖励。


还有其他人有什么建议吗?最主要的是我希望在结果从服务返回时网格能够分部更新 - 目前似乎并没有这样做 - 它不会渲染直到所有结果都返回(除非我在另一个线程中调用服务)。 - Paul
你的代码片段来自于你的代码后台页面吗?在这里很难确定上下文。另外,当你谈论“AJAX实现”时,你指的是什么?你使用了UpdatePanel吗?这里关键的信息可能与ASP.NET相关。 - jwheron
@jwiscarson - 谢谢,我会添加aspx代码 - 是的,您正在看到后台代码。 - Paul
4个回答

2
因为 ASP 的 UpdatePanel,您看到的是所谓的“屏幕锁定”。
ASP.NET WebForms 试图使 Web 行为类似于 Windows 表单。可敬的?这取决于你问谁。
当您使用 UpdatePanel 时,ASP.NET 将服务器控件存储在 ViewState 中,并进行必要的任何更改 (在您的情况下,它基于 Timer_Tick 和 ButtonUpdate_Click 函数中的代码更新页面)。然后,它会使用这些新数据刷新页面,导致您描述的屏幕锁定。
要避免这种情况,您需要使用真正的 AJAX。很多人都使用 jQuery AJAX 函数和以下选项之一来实现此目的:
- ASP.NET WebMethods - WCF 服务 - 分离的 ASP.NET 页面
关于如何通过 jQuery 钩子连接 ASP.NET WebMethods,以及加载 ASP.NET 页面的问题在 SO 上有相当多的问题,但关于 AJAX 和 WCF 的问题就不那么多了。
如果您选择使用 AJAX 和 jQuery,生成的页面将如下所示:
<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="ASForm.aspx.cs" Inherits="Web_Asp_FBMonitor.ASForm" Async="true" EnableSessionState="True" %>
<asp:Content ID="Content1" ContentPlaceHolderID="HeadContent" runat="server">
<!-- include jQuery library here -->
<script language="javascript" type="text/javascript">
    $(document).ready(function () {
        UpdateGrid();

        // Separate AJAX call to another page, WCF service, or ASP.NET WebMethod for the Timer results
    });

    function UpdateGrid() {
        $.ajax({ url: "GridViewResults.aspx",
            done: function (result) {
                $("#gridResults").html(result.d);
            }
        });
    }
</script>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

    <h2>
        ASP.NET Connection Test (Client in ASYNC, Server in ASYNC)
    </h2>

    <p>
        <h3> <span id="timer"></span> </h3>
    </p>

    <p id="gridResults">
    </p>
    <button id="ButtonUpdate" onclick="UpdateGrid();">Update</button>
</asp:Content>

接下来,在一个单独的页面上(我在上面随意称之为GridViewResults.aspx),您需要:

<asp:GridView ID="GridView1" runat="server"></asp:GridView>
<asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="~/Default.aspx">Client Sync Page</asp:HyperLink>
<br />

谢谢 @jwiscarson,我会在有空的时候尝试一下,并希望能够标记为已解决 :-) - Paul
我仍然无法理解在哪里启动任务并在此处收集结果的问题。如果我理解这个例子,代码注释:“//将AJAX调用分离到另一个页面、WCF服务或ASP.NET WebMethod以获取计时器结果”似乎意味着我调用一个服务来更新GridViewResults.aspx,以便它可以包含在当前页面的段落标记中。我对AJAX很陌生,所以我不知道如何从这个页面更新GridViewResults.aspx。 - Paul
我不确定我完全理解了,但我会尽力解释。GridViewResults.aspx 在其 .cs 文件中将拥有所有 GridView 绑定代码,并处理呈现结果的工作。您的主要页面只是一个骨架,上面有最少量的 HTML 和 JavaScript,真正的工作是由子页面完成的。我在那个 jQuery 函数中留下的注释只是为了表明我也去掉了 Timer 控件,而且您需要创建另一个 AJAX/JQ 调用来获取它的数据。 - jwheron
好的,关于计时器的评论让我有些分心。GridViewResults页面的PageLoad事件会包含数据检索和绑定吗?如果我听起来像需要咀嚼过的食物,那我很抱歉。 - Paul
你干得好。不用担心额外的问题,当你使用 AJAX 时,你确实需要重新思考页面设计的方法。 - jwheron
为了完整起见,我稍微更改了JQuery,因为我使用的是jQuery 1.4,所以我捕获了“success”回调而不是“done”。一切正常! - Paul

1
你可能想要查看 ASP.net 中的“异步”页面。这些页面可以让你进行单个 AJAX 回调,并在服务器端异步地执行所有轮询操作。当所有任务都异步返回并且你拥有完整的数据集时,你可以重新绑定网格控件。
文章链接:

http://msdn.microsoft.com/en-us/magazine/cc163725.aspx


好的,根据评论的反馈,您希望在每次更新返回时更新网格。我不确定您是如何从浏览器启动AJAX请求的,但您可能需要考虑异步地从客户端启动这些请求(类似于jQuery在这里非常有用),然后再次使用脚本在获取结果时重新绘制您需要的特定行。


我的页面指令是异步的。问题在于当所有回调函数触发时,页面渲染会锁定几秒钟。 - Paul
嗯,如果我理解正确的话,您正在进行多个回调 - 为什么不进行单个回调,然后让服务器进行多个异步服务调用 - 这应该会有所帮助。 - Paddy
我希望屏幕能够在结果出现时多次更新,以便进行刷新。 - Paul
@Paddy,我觉得Paul想要让屏幕上看起来像是实时更新而不是整个屏幕刷新...如果我理解正确的话。 - TeamWild
@TeamWild - 是的,那正是我想要实现的。 - Paul

1
从您发布的代码来看,似乎您在客户端向服务器发起了一次单独的调用,然后服务器会进行一系列异步调用。当异步结果到达时,它会更新网格,并在最后一个结果到达时将更新后的网格返回给客户端。
您可以通过强制服务器在第一个异步响应到达后返回来解决此问题。但是,仅丢弃未完成的请求可能存在潜在的清理问题。如果您想追求这个选项,如果您能发布管理传入请求的服务器端代码的部分,那将有所帮助。
如果您想在结果到达服务器时即时获取网格更新,则有几种可能更清晰的替代方案:
  • 从客户端发出一个调用,然后在服务器上扇出多个异步调用的方式,改为从客户端发出多个异步JS调用,每个调用在服务器上都是同步的。当每个同步调用完成时,它会返回到客户端。然后客户端的JS代码会定位并更新网格的适当部分。
  • 切换到使用WebSockets。这是一种双向数据连接。服务器可以使用异步请求定期轮询信息,然后将结果随着到达发送给客户端。然后客户端将使用脚本(如上述#1)来更新网格。
  • 使用长轮询。有一个后台线程定期轮询信息,并维护具有当前状态和序列号或时间戳的数据结构。当客户端请求更新时,它传递了它收到的最后一个时间戳或序列号。然后服务器代码查看后台线程的数据结构是否有新内容。如果有,则更新网格并返回。如果没有,则进入睡眠状态(等待共享锁),直到后台线程接收到更新,此时它会发出信号以唤醒请求线程,更新页面以获取最新数据并返回。
这是一些使用jQuery进行异步Ajax调用的示例代码:
$.get('myinfo.ashx', function(data) {
  $('.result').html(data);
})

更多细节请参见:http://api.jquery.com/jQuery.get/


谢谢 - 我已经尝试了长轮询选项(3),这对我来说效果很好。我在客户端JS代码方面比较薄弱,这真的是我想要探索的领域。如果有人能为我发布一个可工作的JS代码示例,我将授予他们答案。 - Paul
非常感谢- @jwiscarson 刚好比您先回答了我,但我也很感激您的回答,因为它非常有用。 - Paul

0

你考虑过使用长轮询/持久连接吗?

现在有一个优秀的框架可用于在ASP.net中实现这一点,叫做SignalR

以下是一些入门文章:

http://www.hanselman.com/blog/AsynchronousScalableWebApplicationsWithRealtimePersistentLongrunningConnectionsWithSignalR.aspx

http://www.amazedsaint.com/2011/11/introduction-ksigdo-knockout-signalr-to.html

对于你描述的问题,我觉得这可能是一个很好的解决方案,因为它可以让屏幕在数据被推送给订阅者时实时更新。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接