5

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

我希望能够从我的服务异步调用方法,这意味着我的 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>

全球.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

4

4 回答 4

9

我下载了你的源代码自己做一些测试。我设法通过将它添加到 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 上调用您的服务
  • 服务器会创建和放弃很多会话
于 2012-07-04T07:55:57.457 回答
4

如果您不需要 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 将阻止写入会话而不会引发异常。写入的值将作为 null 返回。

于 2013-07-11T12:17:36.277 回答
-1

我对 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一个新线程中执行 a,然后是 First()。First() 方法阻塞了当前线程,所以这两行与Thread.Sleep(2000). 这让 Reactive 不受影响。如果您使用此代码运行,您会得到相同的结果,这表明您没有获得多线程。

造成这种情况的第一个合乎逻辑的原因是 missing ConcurrencyMode=ConcurrencyMode.Multiple,我知道您已经尝试过了。下一个可能性是您在内置的 ASP.Net 开发服务器上运行它,除非我弄错了,否则它不支持多线程。您是否在 IIS 实例上运行它?

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

更新罪魁祸首aspNetCompatibilityEnabled="true"在 web.config 中。如果包含它,您会得到提到的行为。如果不包含(默认=false),您将获得多线程。我还不知道这背后的逻辑。将其设置为 false,您将失去此处所述的一些功能,包括 HttpContext 和 ASP.NET 会话。

** 进一步更新 ** 如果关闭aspNetCompatibilityEnabled,则无法使用路由。我已经尝试了许多组合,包括不同的绑定和 transferMode 设置,它们通过似乎工作了一段时间来欺骗你,然后又回到使用同一个线程。我还检查了工作线程并且已经没有想法了。

于 2012-07-03T14:02:22.463 回答
-1

我认为将您的 ConcurrencyMode 更改为 Multiple 如下所示将解决您的线程阻塞问题:

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

}

于 2012-06-28T21:13:29.593 回答