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

标签 c# asp.net ajax multithreading wcf

我有一个使用 AJAX 从页面在 ASP.Net 站点中使用的 WCF REST 服务。

我希望能够从我的服务异步调用方法,这意味着我将在我的 javascript 代码中有回调处理程序,并且当方法完成时,输出将被更新。这些方法应该在不同的线程中运行,因为每个方法将花费不同的时间来完成它们的任务

我的代码处于半工作状态,但发生了一些奇怪的事情,因为我第一次执行代码编译后,它可以工作在不同的线程中运行每个调用 但是后续调用会阻塞服务,这样每个方法调用都必须等到最后一个调用结束才能执行下一个调用。他们在同一个线程上运行。我以前在使用 Page Methods 时遇到过同样的问题,我通过禁用页面中的 session 解决了这个问题,但我还没有想出如何在使用 WCF REST 服务时做同样的事情

注意:方法完成时间(异步运行它们应该只需要 7 秒,结果应该是:Execute1 - Execute3 - Execute2)

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

编译后输出

after compiling

输出后续调用(就是这个问题)

subsequent calls

我会发布代码...我会尽量简化它

服务契约(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 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(相关)

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

全局.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);

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

我刚刚注意到一些事情,编译后的第一次工作,并且最后使用的线程始终是后续调用使用的线程,并且这个线程被阻塞,就像其他线程一样' t disposed 之类的,或者如果最后一个线程由于某种原因被阻塞了

编辑2

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

http://sdrv.ms/P9wW6D

最佳答案

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

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

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

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

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

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

然后在 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 以允许 session :

EnableSessionState="True"

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

It works on my machine

想法是,当对服务的调用传入时,HttpModule 检查 URL,并在必要时删除 ASP.NET_SessionId cookie,这样它就不会到达服务。然后在 Application_PostAcquireRequestState 中,我们立即放弃创建的 session ,这样我们就不会不必要地为大量空 session 消耗服务器内存。

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

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

代价是:

  • 但是您的服务必须在不需要 session cookie 的可识别 URL 上调用
  • 服务器会创建和放弃很多 session

关于c# - 在 ASP.Net 站点中使用 AJAX 调用时,REST WCF 服务会锁定线程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11250109/

相关文章:

c# - 将一个分层列表投影到另一个具有匿名类型(或动态)的分层列表?

asp.net - MVC3 和 Entity Framework

javascript - AJAX 使用或支持哪些 HTTP 方法?

c# - 在 windows phone 7 模拟器上模拟连接不稳定进行测试

c# - 如何在@Html.ActionLink 中添加图像

c# - 如何为多个标签编写一个通用的 ClickEvent?

c# - 如何在 mailmessage.body 部分换行

c# - 在 ASP.Net 的 GridView 中获取 "checked"复选框的 rowID 值

javascript - 关于 JavaScript 或 AJAX 的简单问题

jquery - 即使确认失败,ASP.NET MVC Ajax Post 也会执行 OnComplete