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

Jup*_*aol 5 c# asp.net ajax wcf multithreading

我在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());
    }
 }
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

Global.asax中

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

编辑1

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

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

[ServiceBehavior(
    InstanceContextMode = InstanceContextMode.PerCall,
    ConcurrencyMode = ConcurrencyMode.Multiple
)]
Run Code Online (Sandbox Code Playgroud)

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

Thread.Sleep(2000);
Run Code Online (Sandbox Code Playgroud)

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

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

编辑2

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

http://sdrv.ms/P9wW6D

use*_*080 9

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

<sessionState mode="Off"></sessionState>
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

然后在Global.asax我改变了ServiceRoute注册:

RouteTable.Routes.Add(new ServiceRoute("ServiceFolder/",
    new WebServiceHostFactory(),
    typeof(MyService)));
Run Code Online (Sandbox Code Playgroud)

在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");
    });
});
Run Code Online (Sandbox Code Playgroud)

然后,为了控制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();
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我在web.config中注册了该模块:

<httpModules>
    <add name="TestPreventCookie" type="RestService.TestPreventCookie" />
</httpModules>
Run Code Online (Sandbox Code Playgroud)

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

EnableSessionState="True"
Run Code Online (Sandbox Code Playgroud)

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

它适用于我的机器

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

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

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

代价是:

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