在ASP.Net站点中使用AJAX调用时,REST WCF服务会锁定线程

我在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()); } } 

ASPX页面

    function callMethodAsync(url, data) { $("#message").append("
" + new Date()); $.ajax({ cache: false, type: "POST", async: true, url: url, data: '"de"', contentType: "application/json", dataType: "json", success: function (msg) { $("#message").append("
   " + msg); }, error: function (xhr) { alert(xhr.responseText); } }); } $(function () { $("#callMany").click(function () { $("#message").html(""); callMethodAsync("/Execute1", "hello"); callMethodAsync("/Execute2", "crazy"); callMethodAsync("/Execute3", "world"); }); });

Web.config(相关)

            

Global.asax中

  void Application_Start(object sender, EventArgs e) { RouteTable.Routes.Ignore("{resource}.axd/{*pathInfo}"); RouteTable.Routes.Add(new ServiceRoute("", new WebServiceHostFactory(), typeof(MyService))); } 

编辑1

我尝试了几种组合,但结果是一样的,我正在使用Visual Studio进行测试,但现在我在IIS 7上进行测试,结果相同

我尝试了以下属性的组合:

 [ServiceBehavior( InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Multiple )] 

我也删除了使用Rx,现在我只是模拟像这样的长进程操作:

 Thread.Sleep(2000); 

但结果是一样的…. 在编译和部署之后,(第一次调用)服务正常工作,它在不同的线程上执行,给出了所需的结果,但后续的调用在同一个线程上运行….不明白

我刚刚注意到一些东西,第一次编译后工作,并且最后使用的线程总是后续调用中使用的线程,并且这个线程被阻塞,就像其他线程没有被处置或者什么或者最后一个线程是由于某种原因被阻止

编辑2

这是该项目的完整代码(RestWCF.zip)

http://sdrv.ms/P9wW6D

我下载了你的源代码,我自己做了一些测试。 我设法通过将其添加到web.config来使其工作:

  

该问题似乎与Asp.Net会话处理有关(似乎与WCF会话不同)。

如果没有这个,Fiddler会显示WCF服务响应正在发送一个ASP.NET_SessionId cookie。 您Page级别EnableSessionState="False"不会影响服务。

编辑 :我做了一些测试,看看我是否可以让它工作,而不必关闭整个应用程序的会话状态。 我现在就开始工作了。 我做的是:

  • 在应用程序的根目录下创建一个新文件夹“ServiceFolder”
  • 将服务接口和实现移动到该文件夹

然后在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中注册了该模块:

    

最后,我将Default.aspx更改为允许会话:

 EnableSessionState="True" 

在这些更改之后,服务调用的并行执行起作用。 强制性免责声明:

它适用于我的机器

我们的想法是,当对服务的调用进入时,HttpModule会检查URL,并在必要时删除ASP.NET_SessionId cookie,使其无法访问服务。 然后在Application_PostAcquireRequestState我们立即放弃创建的会话,因此我们不会为了很多空会话而不必要地消耗服务器内存。

使用此解决方案,您将获得:

  • 并行执行服务调用
  • 您仍然可以在应用程序的其他部分使用Session

代价是:

  • 但是,必须在不需要会话cookie的可识别URL上调用您的服务
  • 服务器将创建并放弃大量会话

如果您不需要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会阻止写入会话而不会抛出exception。 写入的值将返回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不受阻碍。 如果使用此代码运行,则会得到相同的结果,表明您没有获得multithreading。

第一个逻辑原因是缺少ConcurrencyMode=ConcurrencyMode.Multiple ,我知道你已经尝试过了。 下一个可能性是你在内置的ASP.Net开发服务器上运行它,除非我弄错了,否则它不支持multithreading。 你是在IIS实例上运行它吗?

我不确定你想要实现什么,但是t.Take(1)是非阻塞的。

更新罪魁祸首是web.config中的aspNetCompatibilityEnabled="true" 。 如果包含它,您将获得所提及的行为。 如果未包含(默认值= false),则会获得multithreading。 我还不知道这背后的逻辑。 如果将其设置为false,则会丢失此处所述的某些function,包括HttpContext和ASP.NET Sessions。

**进一步更新**如果关闭aspNetCompatibilityEnabled ,则无法使用路由。 我尝试了许多组合,包括不同的绑定和transferMode设置,他们通过看似工作一段时间来欺骗你,然后回到使用相同的线程。 我还检查了工作线程,但我的想法很少。