問題の根本は、ASP.Net要求で何が起こるかを理解することだと思います。
各ページには、イベントのパイプラインを含むライフサイクルがあります。詳細については、この回答を確認してください。各リクエストは、現在のアプリケーションのAppDomainからのワーカースレッドによって処理されます。ページパイプラインが完了するまで、応答はユーザーに送信されません。
スレッドについて:
同時に使用可能なスレッドの数は、で構成できますmachine.config
。理解しておくべき重要な点は、これらのスレッド数は固定されているということです。つまり、同時スレッドの最大数に達すると、後続のリクエストはキューに入れられ、キューに入れることができるリクエストの数はわずかになります。サーバーのメモリによって制限されます。
ワーカースレッドが時間のかかる操作を呼び出すと、操作が終了するまでそのスレッドをブロックします。つまり、複数の同時ユーザーがいる場合は、使用可能なすべてのスレッドがブロックされ、新しいリクエストが強制的に配置される可能性があります。キューに入れられ、最悪の場合、503エラーが発生します-サービスを利用できません。
これを防ぐ方法は、バックグラウンドスレッドでこれらの種類のメソッドを呼び出すことです。コードと同様ですが、この場合の動作は期待どおりではありません。コードは、ページリクエストを完了するためにスレッドが終了するのを待っています。これが、同時に結果を受け取る理由です(最後に)リクエストの)。
asp.netページを非同期で実行する方法の詳細については、この優れた記事を参照してください。
今、あなたが必要とする結果を得るために:
(これは完全に機能する例です。この例では、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()
{
}
}
を返す代わりに、次のように、DataSet
を返す方が簡単で管理しやすいと思います。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;
}
}
出力