我正在开发一个ASP.Net应用程序,该应用程序正在执行一些繁重的异步工作,并在工作过程中通过一些javascript代码立即通知客户端。
在测试过程中,我意识到它的工作非常好,直到发现其他问题为止。当单个异步页面打开并开始运行时,IIS将阻止(保留)所有其他以后的客户端请求,直到异步页面完成工作为止。 IIS接受连接(我想只是为了防止浏览器侧的连接超时),但是在异步页面完全完成之前,不发送任何内容。异步作业完成后,它将恢复流程将来的请求,并且一切恢复正常。
但是,这当然是有问题的。老实说,我没想到会这样,因为这就是为什么要使用线程,而不是阻止UI,对吧?
这是我为您开发的示例代码,用于测试谁对该主题感兴趣。您可以创建新项目,甚至将其添加到现有的asp.net应用程序之一。之后,只需转到/async.aspx
,当它开始异步计数时,只需在浏览器中打开另一个标签页,然后开始请求同一域中的其他子页。您将体验到,最终网站将停止响应将来的请求,直到异步工作完成。
我说“最终”是因为 对于此简单示例代码 ,有时IIS会响应第一个到三个请求,然后进入“保留”状态。
但是,在我的主项目中,异步工作比依靠示例代码中的2个线程要重得多,因此,即使在我的工作开始时也无法加载第一页。
async.aspx:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="async.aspx.cs" Inherits="async.test.app.async" Async="true" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Async Test</title>
<script>
function updatePercent(id, p) {
document.getElementById(id).innerText = p;
}
function allDone() {
var labels = document.getElementsByClassName("percent");
for (var i = 0; i < labels.length; i++)
labels[i].innerText = "Completed!";
}
</script>
</head>
<body>
<form id="form1" runat="server">
<h1>Async test page</h1>
<div>Server-side async task #1 percent is: <span id="percent1" class="percent"></span></div>
<div>Server-side async task #2 percent is: <span id="percent2" class="percent"></span></div>
</form>
</body>
</html>
<%
//calling to start right here makes it sure to browser load up the content so far - using response.flush in StartAsyncWork()
StartAsyncWork();
%>
async.aspx.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Threading;
namespace async.test.app
{
public partial class async : System.Web.UI.Page
{
ManualResetEvent[] MRE;
object response_locker;
protected void Page_Load(object sender, EventArgs e)
{
}
public void StartAsyncWork()
{
//flush buffer first
Response.Flush();
response_locker = new object();
//create first task (count to 20)
ManualResetEvent mre1 = new ManualResetEvent(false);
Thread Thread1 = new Thread(() => CountTo(mre1, "percent1", 20));
//create second task (count to 30)
ManualResetEvent mre2 = new ManualResetEvent(false);
Thread Thread2 = new Thread(() => CountTo(mre2, "percent2", 30));
//prepare waithandles
MRE = new ManualResetEvent[] { mre1, mre2 };
//start tasks
Thread1.Start();
Thread2.Start();
//wait for all tasks to complete
ManualResetEvent.WaitAll(MRE);
Response.Write("<script>allDone();</script>");
Response.Flush();
}
protected void CountTo(ManualResetEvent mre, string id, int countTo) //counts to 60
{
try
{
for (int i = 1; i <= countTo; i++)
{
lock (response_locker)
{
Response.Write(string.Format("<script>updatePercent('{0}','{1}/{2}')</script>", id, i, countTo));
Response.Flush();
}
Thread.Sleep(1000);
}
}
catch (Exception)
{
throw;
}
finally
{
mre.Set();
}
}
}
}