5

I have a WCF REST service consumed in an ASP.Net site, from a page, using AJAX.

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

Note: Methods complete time (running them async should take only 7 sec and the result should be: Execute1 - Execute3 - Execute2)

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

Output After compiling

after compiling

Output subsequent calls (this is the problem)

subsequent calls

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

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

<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)));
    }

Edit 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

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:

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

This is the full code of this project (RestWCF.zip)

http://sdrv.ms/P9wW6D

Jupaol
  • 21,107
  • 8
  • 68
  • 100
  • Instead of Observable.Start(...Scheduler.NewThread), try spawning a new thread with Thread t = new Thread(new ThreadStart(DoStuff)); t.Start(); Or use the ThreadPool. – Jon Jun 28 '12 at 17:53
  • It is the same result...Observavble start is part of the Rx framework, I have used it before I knew that was not the problem just wanted to double checked – Jupaol Jun 28 '12 at 18:26
  • I think the problem could be related to instantiating the service, but I added `InstanceContextMode = InstanceContextMode.PerCall` I thought that would be enough... – Jupaol Jun 28 '12 at 18:28
  • Are you using Session State in your Default.aspx.cs? – NickD Jul 01 '12 at 18:07
  • No. I am not, I just double checked. The problem is that the first time **after compiling** it works, but subsequent calls do not work – Jupaol Jul 01 '12 at 18:34
  • I dot get it, why do you want to explicitly run each method in a separate thread? Since you are using per call instancing and you are not in need of a session. – Mihai H Jul 01 '12 at 18:48
  • Please check my edited question if you don't mind – Jupaol Jul 03 '12 at 19:43
  • This may be an over simplistic catch, but you appear to be attempting to use JQuery Ajax without a script reference to JQuery in your .aspx page. – cboler Jul 04 '12 at 01:48
  • I just uploaded the whole project, if you wish you could download it – Jupaol Jul 04 '12 at 03:48

4 Answers4

10

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:

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

The problem seem to be related to the Asp.Net session handling (seems to be different from WCF session).

Without this, Fiddler shows that the WCF service response is sending an ASP.NET_SessionId cookie. You Page level EnableSessionState="False" does not affect the service.

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
  • Moved the service interface and implementation to that folder

Then in Global.asax I changed the ServiceRoute registration:

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

In Default.aspx I changed:

$(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:

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:

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

Finally, I change Default.aspx to allow session:

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

With this solution, you get:

  • Parallell execution of service calls
  • You can still use Session in other parts of your application

At the cost of:

  • But you service must be called on a recognizable URL where session cookies are not needed
  • The server will create and abandon a lot of sessions
user1429080
  • 9,086
  • 4
  • 31
  • 54
  • Nice, this solves the problem, at the cost of disabling the session in the whole site, I am wondering if there's a workaround for this. Also is this attribute being ignored then `SessionMode = SessionMode.NotAllowed`?? – Jupaol Jul 04 '12 at 16:47
  • I'm now away from the comp so I'm not able to test. But at least in theory it might be possible to disable session for the service by moving it to a subfolder and placing a local web.config there with the sessionmode.notallowed. – user1429080 Jul 04 '12 at 16:59
  • @Jupaol Ignore my previous comment. See my updated answer instead. Regarding WCF sessions see for instance [this question](http://stackoverflow.com/questions/7400309/wcf-session-asp-net-session) – user1429080 Jul 05 '12 at 08:10
  • This is a great answer, thank you very much, about the cons, specifically _The server will create and abandon a lot of sessions_ that will only affect calls to the service, so I think that's acceptable for now. – Jupaol Jul 06 '12 at 00:21
4

If you do not need Session or need it ReadOnly, you can change SessionStateBehavior for specific svc in Global.asax.cs. 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. Written values will be returned as null.

LukeSkywalker
  • 341
  • 2
  • 7
  • Thank you for sharing this, it works perfect for my needs. For those interested in using this approach [read this blog post](http://blogs.microsoft.co.il/idof/2010/09/27/aspnet-compatible-wcf-services-concurrency-problem/) – Ross Dec 31 '13 at 19:56
-1

I think changing your ConcurrencyMode to Multiple like below would fix your thread blocking issue:

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

}

John Culviner
  • 22,235
  • 6
  • 55
  • 51
-1

I am only passingly familiar with Reactive, but something stood out that didn't look right.

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(). The First() method blocks the current thread, so these two lines are no different to Thread.Sleep(2000). That gets Reactive out of the way. 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. 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. Are you running it on an IIS instance?

I am not sure what you are trying to achieve, but t.Take(1) is non-blocking.

Update The culprit is aspNetCompatibilityEnabled="true" in web.config. If it is included you get the behavior mentioned. If it is not included (default=false), you get multi-threading. 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.

** Further update ** If you turn off aspNetCompatibilityEnabled, you can't using routing. 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. I also checked worker threads and am running out of ideas.

Michael
  • 8,891
  • 3
  • 29
  • 42
  • I have two applications - an existing one that I added the above methods to, and one based on yours. The one based on yours behaves the same way as yours. However, my existing application creates new threads for each request. I am trying to isolate the difference. – Michael Jul 04 '12 at 01:09
  • I just tried but I'm getting the following error: ASP.NET routing integration feature requires ASP.NET compatibility. Please see 'http://msdn.microsoft.com/en-us/library/ms731336.aspx' on how to do this. Then I tried by changing this: `[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.NotAllowed)]` and it's the same, since I have REST WCF services I require to have `aspNetCompatibilityEnabled="true"` – Jupaol Jul 04 '12 at 02:24
  • This is really annoying, I do not understand why the first time the services run in different threads and whyyy subsequent calls block ALWAYS the last thread used. It has to be something simple but I have not figure it out what – Jupaol Jul 04 '12 at 02:26
  • I just uploaded the whole project, if you wish you could download it – Jupaol Jul 04 '12 at 03:48