小编典典

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

ajax

我使用AJAX从ASP.Net网站的页面中使用了WCF REST服务。

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

我的代码无法正常工作,但是发生了一些奇怪的事情,因为 在编译后 第一次执行代码时 ,它可以正常工作在不同的线程中运行每个调用,
但随后的调用将服务以一种使每个方法调用具有等待直到最后一个调用结束才能执行下一个。它们在同一线程上运行。在使用 Page Methods
之前,我曾遇到过同样的问题,我通过禁用页面中的会话来解决此问题,但是我没有弄清楚在使用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页面

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

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


阅读 281

收藏
2020-07-26

共1个答案

小编典典

我下载了您的源代码以自行进行一些测试。我设法通过将其添加到web.config中使其工作:

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

该问题似乎与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)));

在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_SessionIdcookie,以使其无法到达服务。然后,Application_PostAcquireRequestState我们立即放弃创建的会话,这样就不必在大量空会话中不必要地消耗服务器内存。

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

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

以:

  • 但是您必须在不需要会话cookie的可识别URL上调用服务
  • 服务器将创建并放弃很多会话
2020-07-26