我认为问题的根源在于了解 ASP.Net 请求会发生什么。
每个页面都有其生命周期,其中包括事件管道,有关更多信息,请查看此答案。每个请求都由来自当前应用程序的 AppDomain 的工作线程处理。在页面管道完成之前,不会将响应发送给用户。
关于线程:
同时可用线程的数量可以在machine.config
. 要理解的关键点是这些线程数是固定的,也就是说当达到最大并发线程数时,后续的请求会被放入一个队列中,队列中可以放入的请求数只有受服务器内存限制。
当一个工作线程调用一个耗时的操作时,您会阻塞该线程,直到操作完成,这再次意味着,如果有多个并发用户,则可能所有可用线程都可能被阻塞,从而迫使新的请求被放置在一个排队,在最坏的情况下,导致 503 错误 - 服务不可用。
防止这种情况的方法是在后台线程中调用这些方法。与您的代码类似,但在这种情况下行为不是您所期望的,您的代码正在等待线程结束以完成页面请求,这就是您同时收到结果的原因(最后的请求)。
有关异步执行 asp.net 页面的更多详细信息,请参阅这篇优秀的文章
现在为了得到你需要的结果:
(这是一个完整的工作示例,在此示例中,我使用Rx - Reactive Programming在新线程中运行耗时的方法,如果您愿意,可以将其更改为使用另一个框架,我更喜欢使用 Rx,因为我觉得对 API 更满意)
使用 PageMethods
背后的代码
[WebMethod]
public static string Execute1()
{
JavaScriptSerializer j = new JavaScriptSerializer();
string r = string.Empty;
var o = Observable.Start(() =>
{
Thread.Sleep(2000);
r = "My Name1: " + DateTime.Now.ToString() + " Background thread: " + Thread.CurrentThread.ManagedThreadId.ToString();
}, Scheduler.NewThread);
o.First();
r += " Main thread: " + Thread.CurrentThread.ManagedThreadId.ToString();
r = j.Serialize(new { res = r });
return r;
}
[WebMethod]
public static string Execute2()
{
JavaScriptSerializer j = new JavaScriptSerializer();
string r = string.Empty;
var o = Observable.Start(() =>
{
Thread.Sleep(7000);
r = "My Name2: " + DateTime.Now.ToString() + " Background thread: " + Thread.CurrentThread.ManagedThreadId.ToString();
}, Scheduler.NewThread);
o.First();
r += " Main thread: " + Thread.CurrentThread.ManagedThreadId.ToString();
r = j.Serialize(new { res = r });
return r;
}
[WebMethod]
public static string Execute3()
{
JavaScriptSerializer j = new JavaScriptSerializer();
string r = string.Empty;
var o = Observable.Start(() =>
{
Thread.Sleep(4000);
r = "My Name3: " + DateTime.Now.ToString() + " Background thread: " + Thread.CurrentThread.ManagedThreadId.ToString();
}, Scheduler.NewThread);
o.First();
r += " Main thread: " + Thread.CurrentThread.ManagedThreadId.ToString();
r = j.Serialize(new { res = r });
return r;
}
ASPX
....
<script type="text/javascript" src="Scripts/jquery-1.7.2.min.js"></script>
....
<asp:ScriptManager runat="server" />
<input type="button" id="callAsync" name="callAsync" value="Call Async" />
<div id="first"></div>
<script type="text/javascript">
function onsuccess1(msg) {
var result = Sys.Serialization.JavaScriptSerializer.deserialize(msg.d);
var h = $("#first").html();
$("#first").html( h + "<br/> Result: " + result.res);
}
function onerror1(xhr) {
alert(xhr.responseText);
}
function callMyMethod(url, mydata, onsuccess, onerror) {
var h = $("#first").html();
$("#first").html(h + "<br/> Calling Method: " + new Date());
return $.ajax({
cache: false,
type: "POST",
async: true,
url: url,
data: mydata,
contentType: "application/json",
dataType: "json",
success: function (msg) {
onsuccess(msg);
},
error: function (xhr) {
onerror(xhr);
}
});
}
$(document).ready(function () {
$("#callAsync").click(function () {
var h = $("#first").html();
$("#first").html(h + "<br/>New call: " + new Date());
callMyMethod("DynamicControls.aspx/Execute1", "{}", function (data) { onsuccess1(data); }, function (data) { onerror1(data); });
callMyMethod("DynamicControls.aspx/Execute2", "{}", function (data) { onsuccess1(data); }, function (data) { onerror1(data); });
callMyMethod("DynamicControls.aspx/Execute3", "{}", function (data) { onsuccess1(data); }, function (data) { onerror1(data); });
});
});
</script>
输出
此代码生成以下内容:
New call: Fri Jun 22 02:11:17 CDT 2012
Calling Method: Fri Jun 22 02:11:17 CDT 2012
Calling Method: Fri Jun 22 02:11:17 CDT 2012
Calling Method: Fri Jun 22 02:11:17 CDT 2012
Result: My Name1: 6/22/2012 2:11:19 AM Background thread: 38 Main thread: 48
Result: My Name2: 6/22/2012 2:11:26 AM Background thread: 50 Main thread: 48
Result: My Name3: 6/22/2012 2:11:30 AM Background thread: 52 Main thread: 48
如您所见,代码未优化,主线程被锁定,我们设置的最大秒数为 7,但在这种情况下,从调用服务器代码 (02:11:17) 到最后收到响应 (2:11:30) 13 秒过去了。这是因为 ASP.Net 默认锁定当前Session
对象锁定主线程。由于我们在此示例中没有使用会话,因此我们可以像这样配置页面:
<%@ Page EnableSessionState="False"
新的输出是:
New call: Fri Jun 22 02:13:43 CDT 2012
Calling Method: Fri Jun 22 02:13:43 CDT 2012
Calling Method: Fri Jun 22 02:13:43 CDT 2012
Calling Method: Fri Jun 22 02:13:43 CDT 2012
Result: My Name1: 6/22/2012 2:13:45 AM Background thread: 52 Main thread: 26
Result: My Name3: 6/22/2012 2:13:47 AM Background thread: 38 Main thread: 49
Result: My Name2: 6/22/2012 2:13:50 AM Background thread: 50 Main thread: 51
现在,从第一个服务器方法调用到最后一个没有锁定主线程的响应只用了 7 秒 =)
编辑 1
如果我的理解是正确的,您需要将一些参数传递给方法并返回数据集以填充标签和文本框。
要将参数传递给 PageMethods,这是一种方法:
安装这些 Nuget 包:
命令类。此类将表示您要在 post 操作中发送到服务器方法的参数
public class ProcessXmlFilesCommand
{
public string XmlFilePath { get; set; }
public ProcessXmlFilesCommand()
{
}
}
而不是返回 a DataSet
,我认为返回 a 会更容易和更易于管理IEnumerable
,如下所示:
[WebMethod]
public static IEnumerable<MyResult> ProcessXmlFiles(ProcessXmlFilesCommand command)
{
List<MyResult> results = new List<MyResult>();
var o = Observable.Start(() =>
{
// here do something useful, process your xml files for example
// use the properties of the parameter command
results.Add(new MyResult { SomethingInteresting = DateTime.Now.ToString(), FilePath = command.XmlFilePath + "Processed" });
results.Add(new MyResult { SomethingInteresting = DateTime.Now.ToString(), FilePath = command.XmlFilePath + "Processed" });
results.Add(new MyResult { SomethingInteresting = DateTime.Now.ToString(), FilePath = command.XmlFilePath + "Processed" });
Thread.Sleep(2000);
}, Scheduler.NewThread);
o.First();
return results.AsEnumerable();
}
该类MyResult
表示您要发送回用户的数据
public class MyResult
{
public string SomethingInteresting { get; set; }
public string FilePath { get; set; }
}
在您的 ASPX 页面中
<script type="text/javascript" src="Scripts/jquery-1.7.2.min.js"></script>
<script type="text/javascript" src="Scripts/json2.min.js"></script>
<script type="text/javascript">
function processFiles() {
var filePath = $("#filePath").val();
var command = new processFilesCommand(filePath);
var jsonCommand = JSON.stringify(command);
$.ajax({
cache: false,
type: "POST",
async: true,
url: "CustomThreads.aspx/ProcessXmlFiles",
data: "{'command':" + jsonCommand + "}",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (msg) {
onFileProcessedSuccess(msg);
},
error: function (exc) {
onFileProcessedError(exc);
}
});
}
function onFileProcessedSuccess(msg) {
var response = msg.d;
$.each(response, function (index, myResponse) {
$("#<%:this.myLabel.ClientID %>").append(myResponse.SomethingInteresting + "<br/>");
});
}
function onFileProcessedError(exc) {
alert("Error: " + exc.responseText);
}
function processFilesCommand(filePath) {
this.XmlFilePath = filePath;
}
$(document).ready(function () {
$("#processFile").click(processFiles);
});
</script>
<input type="text" name="filePath" id="filePath" />
<input type="button" name="processFile" id="processFile" value="Process File" />
<br /><asp:Label ID="myLabel" runat="server" />
编辑 2
这是一种简化的方式
<%@ Page EnableSessionState="False" Language="C#" AutoEventWireup="true" CodeBehind="CustomThreadsSimple.aspx.cs" Inherits="WebApplication1.CustomThreadsSimple" %>
....
<script type="text/javascript" src="Scripts/jquery-1.7.2.min.js"></script>
....
<script type="text/javascript">
function makeCall(url, data) {
$("#<%:this.lblMessage.ClientID %>").append("<br/>Initializing call: " + new Date());
$.ajax({
url: url,
type: "POST",
dataType: "json",
contentType: "application/json; charset=utf-8;",
async: true,
cache: false,
data: "{name:'" + data + "'}",
success: function (msg) {
$("#<%:this.lblMessage.ClientID %>").append("<br/> " + msg.d);
},
error: function (exc) {
alert(exc.responseText);
}
});
}
$(function () {
$("#startProcess").click(function () {
makeCall("CustomThreadsSimple.aspx/Execute1", $("#<%: this.txtData1.ClientID %>").val());
makeCall("CustomThreadsSimple.aspx/Execute2", $("#<%: this.txtData2.ClientID %>").val());
makeCall("CustomThreadsSimple.aspx/Execute3", $("#<%: this.txtData3.ClientID %>").val());
});
});
</script>
<asp:TextBox runat="server" ID="txtData1" />
<asp:TextBox runat="server" ID="txtData2" />
<asp:TextBox runat="server" ID="txtData3" />
<input type="button" name="startProcess" id="startProcess" value="Start execution" />
<asp:Label ID="lblMessage" runat="server" />
背后的代码
public partial class CustomThreadsSimple : System.Web.UI.Page
{
[WebMethod]
public static string Execute1(string name)
{
string res = string.Empty;
Func<string, string> asyncMethod = x =>
{
Thread.Sleep(2000);
return "Res1: " + x +" " + DateTime.Now.ToString() + " Background thread: " + Thread.CurrentThread.ManagedThreadId.ToString();
};
IAsyncResult asyncRes = asyncMethod.BeginInvoke(name, null, null);
res = asyncMethod.EndInvoke(asyncRes);
res += " Main thread: " + Thread.CurrentThread.ManagedThreadId.ToString();
return res;
}
[WebMethod]
public static string Execute2(string name)
{
string res = string.Empty;
Func<string, string> asyncMethod = x =>
{
Thread.Sleep(7000);
return "Res2: " + x + " " + DateTime.Now.ToString() + " Background thread: " + Thread.CurrentThread.ManagedThreadId.ToString();
};
IAsyncResult asyncRes = asyncMethod.BeginInvoke(name, null, null);
res = asyncMethod.EndInvoke(asyncRes);
res += " Main thread: " + Thread.CurrentThread.ManagedThreadId.ToString();
return res;
}
[WebMethod]
public static string Execute3(string name)
{
string res = string.Empty;
Func<string, string> asyncMethod = x =>
{
Thread.Sleep(4000);
return "Res3: " + x + " " + DateTime.Now.ToString() + " Background thread: " + Thread.CurrentThread.ManagedThreadId.ToString();
};
IAsyncResult asyncRes = asyncMethod.BeginInvoke(name, null, null);
res = asyncMethod.EndInvoke(asyncRes);
res += " Main thread: " + Thread.CurrentThread.ManagedThreadId.ToString();
return res;
}
}
输出