[英]REST WCF service locks thread when called using AJAX in an ASP.Net site
我在ASP.Net站點中使用AJAX在頁面中使用了WCF REST服務。
我希望能夠從我的服務異步中調用方法,這意味着我將在我的javascript代碼中使用回調處理程序,並且當方法完成時,輸出將被更新。 這些方法應該在不同的線程中運行,因為每個方法都需要不同的時間來完成它們的任務
我有代碼半工作,但是發生了一些奇怪的事情,因為我第一次在編譯后執行代碼,它工作 , 在不同的線程中運行每個調用但后續調用blocs服務,每個方法調用都有這樣的方式等到最后一次通話結束才能執行下一次通話。 他們正在同一個線程上運行。 我在使用頁面方法之前遇到了同樣的問題,我通過禁用頁面中的會話解決了這個問題,但我還沒想到在使用WCF REST服務時如何做同樣的事情
注意:方法完成時間(運行它們異步應該只需要7秒 ,結果應該是: Execute1 - Execute3 - Execute2 )
Execute1
- > 2秒 Execute2
- > 7秒 Execute3
- > 4秒 我會發布代碼......我會盡量簡化它
[ServiceContract(
SessionMode = SessionMode.NotAllowed
)]
public interface IMyService
{
// I have other 3 methods like these: Execute2 and Execute3
[OperationContract]
[WebInvoke(
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json,
UriTemplate = "/Execute1",
Method = "POST")]
string Execute1(string param);
}
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(
InstanceContextMode = InstanceContextMode.PerCall
)]
public class MyService : IMyService
{
// I have other 3 methods like these: Execute2 (7 sec) and Execute3(4 sec)
public string Execute1(string param)
{
var t = Observable.Start(() => Thread.Sleep(2000), Scheduler.NewThread);
t.First();
return string.Format("Execute1 on: {0} count: {1} at: {2} thread: {3}", param, "0", DateTime.Now.ToString(), Thread.CurrentThread.ManagedThreadId.ToString());
}
}
<%@ Page EnableSessionState="False" Title="Home Page" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true"
CodeBehind="Default.aspx.cs" Inherits="RestService._Default" %>
<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
<script type="text/javascript">
function callMethodAsync(url, data) {
$("#message").append("<br/>" + new Date());
$.ajax({
cache: false,
type: "POST",
async: true,
url: url,
data: '"de"',
contentType: "application/json",
dataType: "json",
success: function (msg) {
$("#message").append("<br/> " + msg);
},
error: function (xhr) {
alert(xhr.responseText);
}
});
}
$(function () {
$("#callMany").click(function () {
$("#message").html("");
callMethodAsync("/Execute1", "hello");
callMethodAsync("/Execute2", "crazy");
callMethodAsync("/Execute3", "world");
});
});
</script>
</asp:Content>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
<input type="button" id="callMany" value="Post Many" />
<div id="message">
</div>
</asp:Content>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true" />
</system.webServer>
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
<standardEndpoints>
<webHttpEndpoint>
<standardEndpoint name="" helpEnabled="true" automaticFormatSelectionEnabled="true" />
</webHttpEndpoint>
</standardEndpoints>
</system.serviceModel>
void Application_Start(object sender, EventArgs e)
{
RouteTable.Routes.Ignore("{resource}.axd/{*pathInfo}");
RouteTable.Routes.Add(new ServiceRoute("",
new WebServiceHostFactory(),
typeof(MyService)));
}
我嘗試了幾種組合,但結果是一樣的,我正在使用Visual Studio進行測試,但現在我在IIS 7上進行測試,結果相同
我嘗試過以下屬性的組合:
[ServiceBehavior(
InstanceContextMode = InstanceContextMode.PerCall,
ConcurrencyMode = ConcurrencyMode.Multiple
)]
我也刪除了使用Rx,現在我只是模擬像這樣的長進程操作:
Thread.Sleep(2000);
但結果是一樣的.... 在編譯和部署之后,(第一次調用)服務正常工作,它在不同的線程上執行,給出了所需的結果,但后續調用在同一個線程上運行....不明白
我剛剛注意到一些東西,第一次編譯后工作,並且最后使用的線程總是后續調用中使用的線程,並且這個線程被阻塞,就像其他線程沒有處理或什么或者最后一個線程是由於某種原因被阻止
這是這個項目的完整代碼(RestWCF.zip)
我下載了你的源代碼,我自己做了一些測試。 我設法通過將其添加到web.config來使其工作:
<sessionState mode="Off"></sessionState>
該問題似乎與Asp.Net會話處理有關(似乎與WCF會話不同)。
如果沒有這個,Fiddler會顯示WCF服務響應正在發送一個ASP.NET_SessionId
cookie。 您Page
級別EnableSessionState="False"
不會影響服務。
編輯 :我做了一些測試,看看我是否可以讓它工作而不必關閉整個應用程序的會話狀態。 我現在就開始工作了。 我做的是:
然后在Global.asax
我更改了ServiceRoute
注冊:
RouteTable.Routes.Add(new ServiceRoute("ServiceFolder/",
new WebServiceHostFactory(),
typeof(MyService)));
在Default.aspx中,我更改了:
$(function () {
$("#callMany").click(function () {
$("#message").html("");
callMethodAsync('<%=this.ResolveUrl("~/ServiceFolder/Execute1") %>', "hello");
callMethodAsync('<%=this.ResolveUrl("~/ServiceFolder/Execute2") %>', "crazy");
callMethodAsync('<%=this.ResolveUrl("~/ServiceFolder/Execute3") %>', "world");
});
});
然后,為了控制cookie處理,我創建了一個HttpModule
:
using System;
using System.Web;
namespace RestService
{
public class TestPreventCookie : IHttpModule
{
public void Dispose()
{
}
public void Init(HttpApplication application)
{
application.BeginRequest +=
(new EventHandler(this.Application_BeginRequest));
application.PostAcquireRequestState +=
(new EventHandler(this.Application_PostAcquireRequestState));
}
private void Application_BeginRequest(Object source, EventArgs e)
{
//prevent session cookie from reaching the service
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
if (context.Request.Path.StartsWith("/ServiceFolder"))
{
context.Request.Cookies.Remove("ASP.NET_SessionId");
}
}
private void Application_PostAcquireRequestState(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
if (context.Request.Path.StartsWith("/ServiceFolder"))
{
var s = context.Session;
if (s != null)
s.Abandon();
}
}
}
}
然后我在web.config中注冊了該模塊:
<httpModules>
<add name="TestPreventCookie" type="RestService.TestPreventCookie" />
</httpModules>
最后,我將Default.aspx更改為允許會話:
EnableSessionState="True"
在這些更改之后,服務調用的並行執行起作用。 強制性免責聲明:
它適用於我的機器
我們的想法是,當對服務的調用進入時,HttpModule會檢查URL,並在必要時刪除ASP.NET_SessionId
cookie,使其無法訪問服務。 然后在Application_PostAcquireRequestState
我們立即放棄創建的會話,因此我們不會為了很多空會話而不必要地消耗服務器內存。
使用此解決方案,您將獲得:
代價是:
如果您不需要Session或需要ReadOnly,則可以在Global.asax.cs中更改特定svc的SessionStateBehavior。 順序阻止將停止。
protected void Application_BeginRequest(object sender, EventArgs e) {
if (Request.Path.Contains("AjaxTestWCFService.svc")) {
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.ReadOnly);
}
}
但是請注意,SessionStateBehavior.ReadOnly會阻止寫入會話而不會拋出異常。 寫入的值將返回null。
我認為如下所示將ConcurrencyMode更改為Multiple將修復線程阻塞問題:
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(
InstanceContextMode = InstanceContextMode.PerCall,
ConcurrencyMode = ConcurrencyMode.Multiple
)]
public class MyService : IMyService
{
}
我只是熟悉Reactive,但有些東西看起來並不合適。
public string Execute1(string param)
{
var t = Observable.Start(() => Thread.Sleep(2000), Scheduler.NewThread);
t.First();
return string.Format("Execute1 on: {0} count: {1} at: {2} thread: {3}", param, "0", DateTime.Now.ToString(), Thread.CurrentThread.ManagedThreadId.ToString());
}
在此代碼中,您在新線程中執行Thread.Sleep
,然后是First()。 First()方法阻塞當前線程,因此這兩行與Thread.Sleep(2000)
沒有區別。 這讓Reactive不受影響。 如果使用此代碼運行,則會得到相同的結果,表明您沒有獲得多線程。
第一個邏輯原因是缺少ConcurrencyMode=ConcurrencyMode.Multiple
,我知道你已經嘗試過了。 下一個可能性是你在內置的ASP.Net開發服務器上運行它,除非我弄錯了,否則它不支持多線程。 你是在IIS實例上運行它嗎?
我不確定你想要實現什么,但是t.Take(1)是非阻塞的。
更新罪魁禍首是web.config中的aspNetCompatibilityEnabled="true"
。 如果包含它,您將獲得所提及的行為。 如果未包含(默認值= false),則會獲得多線程。 我還不知道這背后的邏輯。 如果將其設置為false,則會丟失此處所述的某些功能,包括HttpContext和ASP.NET Sessions。
**進一步更新**如果關閉aspNetCompatibilityEnabled
,則無法使用路由。 我已經嘗試了許多組合,包括不同的綁定和transferMode設置,它們通過看似工作一段時間來欺騙你,然后回到使用相同的線程。 我還檢查了工作線程,但我的想法已經用完了。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.