5

ASP.Netサイトで、AJAXを使用してページからWCFRESTサービスを使用しています。

サービスasyncからメソッドを呼び出せるようにしたいのですが、これはjavascriptコードにコールバックハンドラーがあり、メソッドが終了すると出力が更新されることを意味します。各メソッドはタスクを完了するのに異なる時間がかかるため、メソッドは異なるスレッドで実行する必要があります

コードは半ば機能していますが、コンパイル後に初めてコードを実行すると機能し、各呼び出しを異なるスレッドで実行しますが、後続の呼び出しは各メソッド呼び出しが持つようにサービスをブロックするため、何か奇妙なことが起こってます次の呼び出しを実行するために、最後の呼び出しが終了するまで待機します。そして、それらは同じスレッドで実行されています。以前にページメソッドを使用していたときに同じ問題が発生し、ページのセッションを無効にすることで問題を解決しましたが、WCFRESTサービスを使用するときに同じ問題を解決する方法がわかりませんでした

注:メソッドの完了時間(非同期で実行するのにかかる時間はわずか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

いくつかの組み合わせを試しましたが、結果は同じです。VisualStudioを使用してテストしていましたが、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

以下のようにConcurrencyModeをMultipleに変更すると、スレッドブロッキングの問題が修正されると思います。

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

}

于 2012-06-28T21:13:29.593 に答える
-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では、新しいスレッドでを実行し、その後にFirst()が続きます。First()メソッドは現在のスレッドをブロックするため、これらの2行は。と同じThread.Sleep(2000)です。それは邪魔にならないようにリアクティブになります。このコードを使用して実行すると、同じ結果が得られます。これは、マルチスレッドを取得していないことを示しています。

これの最初の論理的な原因は欠落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 に答える