简体   繁体   English

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

[英]REST WCF service locks thread when called using AJAX in an ASP.Net site

I have a WCF REST service consumed in an ASP.Net site, from a page, using AJAX. 我在ASP.Net站点中使用AJAX在页面中使用了WCF REST服务。

I want to be able to call methods from my service async, which means I will have callback handlers in my javascript code and when the methods finish, the output will be updated. 我希望能够从我的服务异步中调用方法,这意味着我将在我的javascript代码中使用回调处理程序,并且当方法完成时,输出将被更新。 The methods should run in different threads, because each method will take different time to complete their task 这些方法应该在不同的线程中运行,因为每个方法都需要不同的时间来完成它们的任务

I have the code semi-working, but something strange is happening because the first time I execute the code after compiling, it works , running each call in a different threads but subsequent calls blocs the service, in such a way that each method call has to wait until the last call ends in order to execute the next one. 我有代码半工作,但是发生了一些奇怪的事情,因为我第一次在编译后执行代码,它工作在不同的线程中运行每个调用但后续调用blocs服务,每个方法调用都有这样的方式等到最后一次通话结束才能执行下一次通话。 And they are running on the same thread. 他们正在同一个线程上运行。 I have had the same problem before when I was using Page Methods , and I solved it by disabling the session in the page but I have not figured it out how to do the same when consuming WCF REST services 我在使用页面方法之前遇到了同样的问题,我通过禁用页面中的会话解决了这个问题,但我还没想到在使用WCF REST服务时如何做同样的事情

Note: Methods complete time (running them async should take only 7 sec and the result should be: Execute1 - Execute3 - Execute2 ) 注意:方法完成时间(运行它们异步应该只需要7秒 ,结果应该是: Execute1 - Execute3 - Execute2

  • Execute1 --> 2 sec Execute1 - > 2秒
  • Execute2 --> 7 sec Execute2 - > 7秒
  • Execute3 --> 4 sec Execute3 - > 4秒

Output After compiling 输出编译后

编译后

Output subsequent calls (this is the problem) 输出后续调用(这是问题)

后续电话

I will post the code...I'll try to simplify it as much as I can 我会发布代码......我会尽量简化它

Service Contract 服务合约

[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 page ASPX页面

<%@ 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/>&nbsp;&nbsp;&nbsp;" + 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>

Web.config (relevant) Web.config(相关)

<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>

Global.asax Global.asax中

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

Edit 1 编辑1

I have tried several combinations but the result is the same, I was testing using Visual Studio but now I am testing on IIS 7, and it's the same result 我尝试了几种组合,但结果是一样的,我正在使用Visual Studio进行测试,但现在我在IIS 7上进行测试,结果相同

I have tried combinations of the following properties: 我尝试过以下属性的组合:

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

Also I removed the use of Rx, now I am just simulating long-process operations like this: 我也删除了使用Rx,现在我只是模拟像这样的长进程操作:

Thread.Sleep(2000);

But the result is the same.... After compiling, and deploying, (the first call) the service works correctly, it is executed on different threads giving the desired result, but subsequent calls run on the same thread.... I do not get it 但结果是一样的.... 在编译和部署之后,(第一次调用)服务正常工作,它在不同的线程上执行,给出了所需的结果,但后续调用在同一个线程上运行....不明白

I just noticed something, the first time after compiling works, and the last thread used is always the thread used on subsequent calls, and this thread is blocked, it's like if the other threads weren't disposed or something or if the last thread were blocked for some reason 我刚刚注意到一些东西,第一次编译后工作,并且最后使用的线程总是后续调用中使用的线程,并且这个线程被阻塞,就像其他线程没有处理或什么或者最后一个线程是由于某种原因被阻止

Edit 2 编辑2

This is the full code of this project (RestWCF.zip) 这是这个项目的完整代码(RestWCF.zip)

http://sdrv.ms/P9wW6D http://sdrv.ms/P9wW6D

I downloaded your source code to do some testing on my own. 我下载了你的源代码,我自己做了一些测试。 I managed to get it to work by adding this to the web.config: 我设法通过将其添加到web.config来使其工作:

<sessionState mode="Off"></sessionState>

The problem seem to be related to the Asp.Net session handling (seems to be different from WCF session). 该问题似乎与Asp.Net会话处理有关(似乎与WCF会话不同)。

Without this, Fiddler shows that the WCF service response is sending an ASP.NET_SessionId cookie. 如果没有这个,Fiddler会显示WCF服务响应正在发送一个ASP.NET_SessionId cookie。 You Page level EnableSessionState="False" does not affect the service. Page级别EnableSessionState="False"不会影响服务。

EDIT : I did some more testing to see if I could get it to work without having to turn off session state for the whole application. 编辑 :我做了一些测试,看看我是否可以让它工作而不必关闭整个应用程序的会话状态。 I got it working now. 我现在就开始工作了。 What I did was: 我做的是:

  • Create a new folder "ServiceFolder" under the root of the application 在应用程序的根目录下创建一个新文件夹“ServiceFolder”
  • Moved the service interface and implementation to that folder 将服务接口和实现移动到该文件夹

Then in Global.asax I changed the ServiceRoute registration: 然后在Global.asax我更改了ServiceRoute注册:

RouteTable.Routes.Add(new ServiceRoute("ServiceFolder/",
    new WebServiceHostFactory(),
    typeof(MyService)));

In Default.aspx I changed: 在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");
    });
});

Then, in order to control the cookie handling, I created an HttpModule : 然后,为了控制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();
            }
        }
    }
}

And then I registered the module in web.config: 然后我在web.config中注册了该模块:

<httpModules>
    <add name="TestPreventCookie" type="RestService.TestPreventCookie" />
</httpModules>

Finally, I change Default.aspx to allow session: 最后,我将Default.aspx更改为允许会话:

EnableSessionState="True"

After these changes, the parallell execution of the service call works. 在这些更改之后,服务调用的并行执行起作用。 Obligatory disclaimer: 强制性免责声明:

It works on my machine 它适用于我的机器

The idea is that when the calls to the service are coming in, the HttpModule inspects the URL and, when necessary, removes the ASP.NET_SessionId cookie so it doesn't reach the service. 我们的想法是,当对服务的调用进入时,HttpModule会检查URL,并在必要时删除ASP.NET_SessionId cookie,使其无法访问服务。 And then in the Application_PostAcquireRequestState we immediately abandon the created session so we don't unnecessarily consume server memory for a lot of empty sessions. 然后在Application_PostAcquireRequestState我们立即放弃创建的会话,因此我们不会为了很多空会话而不必要地消耗服务器内存。

With this solution, you get: 使用此解决方案,您将获得:

  • Parallell execution of service calls 并行执行服务调用
  • You can still use Session in other parts of your application 您仍然可以在应用程序的其他部分使用Session

At the cost of: 代价是:

  • But you service must be called on a recognizable URL where session cookies are not needed 但是,必须在不需要会话cookie的可识别URL上调用您的服务
  • The server will create and abandon a lot of sessions 服务器将创建并放弃大量会话

If you do not need Session or need it ReadOnly, you can change SessionStateBehavior for specific svc in Global.asax.cs. 如果您不需要Session或需要ReadOnly,则可以在Global.asax.cs中更改特定svc的SessionStateBehavior。 The sequential blocking will stop. 顺序阻止将停止。

protected void Application_BeginRequest(object sender, EventArgs e) {
   if (Request.Path.Contains("AjaxTestWCFService.svc")) {
      HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.ReadOnly);
   }
}

Be warned however that SessionStateBehavior.ReadOnly will prevent writing to sessions without throwing exception. 但是请注意,SessionStateBehavior.ReadOnly会阻止写入会话而不会抛出异常。 Written values will be returned as null. 写入的值将返回null。

I think changing your ConcurrencyMode to Multiple like below would fix your thread blocking issue: 我认为如下所示将ConcurrencyMode更改为Multiple将修复线程阻塞问题:

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(
    InstanceContextMode = InstanceContextMode.PerCall,
    ConcurrencyMode = ConcurrencyMode.Multiple
)]
public class MyService : IMyService
{

} }

I am only passingly familiar with Reactive, but something stood out that didn't look right. 我只是熟悉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());
}

In this code you are doing a Thread.Sleep in a new thread, followed by First(). 在此代码中,您在新线程中执行Thread.Sleep ,然后是First()。 The First() method blocks the current thread, so these two lines are no different to Thread.Sleep(2000) . First()方法阻塞当前线程,因此这两行与Thread.Sleep(2000)没有区别。 That gets Reactive out of the way. 这让Reactive不受影响。 If you run with this code you get the same result, indicating that you are not getting multithreading. 如果使用此代码运行,则会得到相同的结果,表明您没有获得多线程。

The first logical cause of this is missing ConcurrencyMode=ConcurrencyMode.Multiple , which I understand you have tried. 第一个逻辑原因是缺少ConcurrencyMode=ConcurrencyMode.Multiple ,我知道你已经尝试过了。 The next possibility is that you are running it on the built-in ASP.Net Development Server which, unless I am mistaken, does not support multithreading. 下一个可能性是你在内置的ASP.Net开发服务器上运行它,除非我弄错了,否则它不支持多线程。 Are you running it on an IIS instance? 你是在IIS实例上运行它吗?

I am not sure what you are trying to achieve, but t.Take(1) is non-blocking. 我不确定你想要实现什么,但是t.Take(1)是非阻塞的。

Update The culprit is aspNetCompatibilityEnabled="true" in web.config. 更新罪魁祸首是web.config中的aspNetCompatibilityEnabled="true" If it is included you get the behavior mentioned. 如果包含它,您将获得所提及的行为。 If it is not included (default=false), you get multi-threading. 如果未包含(默认值= false),则会获得多线程。 I don't yet know the logic behind this. 我还不知道这背后的逻辑。 With it set to false you lose some features as described here , including HttpContext and ASP.NET Sessions. 如果将其设置为false,则会丢失此处所述的某些功能,包括HttpContext和ASP.NET Sessions。

** Further update ** If you turn off aspNetCompatibilityEnabled , you can't using routing. **进一步更新**如果关闭aspNetCompatibilityEnabled ,则无法使用路由。 I have tried a number of combinations including different bindings and transferMode settings, and they fool you by seeming to work for a while, and then going back to using the same thread. 我已经尝试了许多组合,包括不同的绑定和transferMode设置,它们通过看似工作一段时间来欺骗你,然后回到使用相同的线程。 I also checked worker threads and am running out of ideas. 我还检查了工作线程,但我的想法已经用完了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM