I'm developing an ASP.Net application that is doing some heavy async works and informing the client-side instantly by some javascript codes during the work.
During my tests, I've realized it's working very good until I've discovered something else. When that single async page is opened and started to run, all other future client requests are blocked (held) by the IIS until the async page completes the work. IIS accepts the connection (I guess only to prevent connection timeouts on the browser side) but sends absolutely nothing until the async page completely finishes. When the async job completes, it resumes the process future request and everything goes back to normal.
But, of course, this is problematic. I honestly did not expect this coming since this is why threads are being used, to not to block the UI, right?
Here is the sample code I've developed for you to test who is interested in the topic. You can create new project or even add it to one of the existing asp.net application. After doing so, just go to /async.aspx
and when it started to counts asynchronously, just open up another tabpage on the browser and start requesting some other subpages on the same domain. You will experience that eventually the website stops responding to future request until async work is finished.
I say 'eventually' because for this simple example code sometimes IIS responds for the first one to three requests and then goes "on hold" state.
BUT, in my main project, the async work is much heavier than counting on 2 threads in the sample code, therefore it gets impossible to load even the very first page when my job is started.
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();
}
}
}
}