可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have a service that performs a slow task, when its finished I want to update the client using AJAX with the result of the task. In my client I call the task many times to update a grid of results. For the purposes of understanding, its a connection tester that loops through a list of connections to see if they are alive.
I have implemented the service as WCF. I generate async methods when I add the service reference to my web client.
The code works fine, however the screen locks momentarily when the callbacks fire - I think this is because they all happen one after the other and all of them repaint the GridView within quick succession.
I don't want this glitch to happen -I was hoping the AJAX implementation would be able to partially update the GridView as results come back from the service via the callbacks.
The only way I can make this look nice is to launch the async calls in a separate client thread and then use a timer to repaint the data to the grid (the same data that's being updated in the separate thread via the callbacks).
I'm doing this mini-project as a learning exercise then I aim to do the same with MVC3 to know the differences.
Code Snippet (without separate thread, causing screen rendering to slow down during callback):
//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);
}
The question is - can this be done in a better way in asp.net / AJAX? (To avoid the rendering lock up problems and get the grid partially updating as results come in) I don't really want to use a separate client thread such as the following snippet:
// 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);
});
UPDATE:
Adding Page Markup as requested:
<%@ 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--%>
<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--%>
<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>
UPDATE 2:
I'm looking for a concise client side JS example of this. The received options are good and have been greatly appreciated but the client side JS is the one I am stuggling with through my own inexperience and will be awarding the bounty for this.
回答1:
You see what you call "screen locks" because of the ASP UpdatePanel
s.
ASP.NET WebForms are an attempt to make the web act like Windows forms. Admirable? Depends on who you ask.
When you use an UpdatePanel
, ASP.NET stores the server-side controls in ViewState
, makes any changes to that ViewState that is necessary (in your case, it's updating the page based on code in your Timer_Tick
and ButtonUpdate_Click
functions). Then, it refreshes the page with this new data, causing the screen locks you describe.
To get around this, you'll have to use real AJAX. A lot of people do this with jQuery AJAX functions and one of the following options:
- ASP.NET WebMethods
- WCF services
- Separate ASP.NET pages
There are quite a few questions here on SO about hooking ASP.NET WebMethods up via jQuery, and some about loading ASP.NET pages, but not as many questions about AJAX and WCF.
If you choose to use AJAX and jQuery, your resulting page would look something like this:
<%@ 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>
Then, on a separate page (I arbitrarily called it GridViewResults.aspx
above), you'd have:
<asp:GridView ID="GridView1" runat="server"></asp:GridView>
<asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="~/Default.aspx">Client Sync Page</asp:HyperLink>
<br />
回答2:
You may want to have a look at 'async' pages in ASP.net. These would allow you to make a single AJAX callback and have the server do all the polling asynchronously on the server side. You could then rebind the grid when all the tasks have asynchronously returned and you have a full set of data.
Link to an article explaining thsi:
http://msdn.microsoft.com/en-us/magazine/cc163725.aspx
OK, based on feedback from the comments, you want to update the grid as each update returns. I'm not sure how you are kicking off your AJAX requests from the browser, but you may want to look at kicing these off asynchronously from the client (something like jQuery can be useful here) and then using script again to redraw the particular row of the grid that you need when you get a result.
回答3:
From the code you posted, it looks like you make a single call from the client to the server, and the server then makes a bunch of async calls. When the async results arrive, it updates the Grid, and when the last of the results arrive, it returns the updated grid to the client.
You might be able to fix this by forcing the server to return after the very first async response arrives. However, there are potential clean-up issues with just dropping the outstanding requests. If you want to pursue that option, it would help if you could post the part of your server-side code that manages the incoming requests.
If you want to get updates on the grid as the results arrive on the server, there are a few potentially cleaner alternatives:
Instead of issuing one call from the client that fans out to multiple async calls on the server, issue multiple async JS calls from the client, with one synchronous call each on the server. As each sync call completes, it returns to the client. The client-side JS code would then locate and update the appropriate part of your grid.
Switch to using WebSockets. This is a bi-directional data connection. The server could periodically poll for info using async requests, and then send the results to the client as they arrive. The client would then use script, as in #1 above, to update the grid.
Use long polling. Have a background thread that periodically polls for info, and maintains data structures with the current state and either a sequence number or a timestamp. When the client makes an Ajax request for an update, it passes the last timestamp or sequence number it received. The server code then looks to see if there's anything new in the background thread's data structures. If so, it updates the grid and returns. If not, it goes to sleep (waits on a shared lock) until the background thread receives an update, at which time it signals the lock, causing the request thread to wake up, update the page with the latest data, and return.
Here's some sample code using jQuery to make an async Ajax call:
$.get('myinfo.ashx', function(data) {
$('.result').html(data);
})
More details at: http://api.jquery.com/jQuery.get/
回答4:
Have you considered using long polling/persistent connection.
There is now an excellent framework available to make this easy to implement in ASP.net called SignalR.
Here are a couple of articles to get you started:
http://www.hanselman.com/blog/AsynchronousScalableWebApplicationsWithRealtimePersistentLongrunningConnectionsWithSignalR.aspx
http://www.amazedsaint.com/2011/11/introduction-ksigdo-knockout-signalr-to.html
For the problem you are describing it sounds to me like this could be a good fit as it would allow the screen to update as and when data is pushed to the subscriber.