パフォーマンス最適化の概要(5):CSLAサービス側がマルチスレッドのソリューションをどのように使用するか


前編では,非同期スレッドを用いてデータのプリロードを実現し,システム性能を向上させることについて述べた.
このような操作は、一般に、ユーザの待ち時間を短縮するためにクライアントで実行される.クライアントは複数回の非同期要求を送信し、サービス側に到着した後、サービス側がマルチスレッド処理操作をサポートしない場合、各要求を線形に処理すると、必然的にクライアントの非同期要求が意味を持たなくなる.
誰がサービス側を単一スレッドに設計するのか、明らかな間違いではないでしょうか.はい!しかし、私たちのシステムはCSLAを分散を実現するためのフレームワークとして使用していますが、そのサービス・エンド・プログラムは単一スレッドしかサポートできません......この問題はずっと解決したいと思っていましたが、CSLA公式フォーラムを調べたところ、著者はGlobalContextとClientContextのいくつかの理由で、マルチスレッドは一時的にサポートされていないと話しています.火が大きいのに,これはどうして使うのか.仕方なく現在システムはすでにこの枠組みに大きく依存しており、しばらくは新しいものを交換しようとするのも現実的ではない.だから自分でCSLAのコードを修正するしかありません.
 
WCF通信クラスの変更
マルチスレッドのサービス側に変更するには,まずサービス側の要求処理から着手しなければならない.NET3.5のCSLAフレームワークはWCFを用いてデータ伝送を実現する.サーバ側でこのクラスを使用して受信します.
namespace Csla.Server.Hosts
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class WcfPortal : IWcfPortal { }
}

このクラスにはConcurrencyMode=ConcurrencyModeが表記されていないことがわかる.MultipleとUseSynchronizationContext=falseであるため、単一スレッド操作として設計されている.ここでは、装飾モードを使用して新しいクラスを構築します.
/// <summary>
///    ConcurrencyMode = ConcurrencyMode.Multiple
///         
/// </summary>
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall,
ConcurrencyMode = ConcurrencyMode.Multiple,
UseSynchronizationContext = false)]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class MultiThreadsWCFPortal : IWcfPortal
{
    private WcfPortal _innerPortal = new WcfPortal();
    #region IWcfPortal Members
    public WcfResponse Create(CreateRequest request)
    {
        return this._innerPortal.Create(request);
    }
    //...
    #endregion
}

また、プロファイルとクラスのインスタンス化の2つのコードを置き換える必要があります.
app.config:
<services>
    <!--Csla.Server.Hosts.WcfPortal-->
    <service name="OpenExpressApp.Server.WPFHost.MultiThreadsWCFPortal" behaviorConfiguration="returnFaults">
.....
</service>
</services>

 
factory method:
private static Type GetServerHostType()
{
    return typeof(OpenExpressApp.Server.WPFHost.MultiThreadsWCFPortal);
    //return typeof(Csla.Server.Hosts.WcfPortal);
}

これにより、サービス側がリクエストを受信すると、複数のスレッドが自動的にオープンしてリクエストに応答する.また、装飾モードの使用により、ソースコードを変更する必要がなくなります.
 
ApplicationContext._の変更义齿
以上の操作で修正した後、WCFレベルでマルチスレッドが実装されています.ただし、アプリケーションを再実行すると、NullReferenceException異常が放出されます.コードはここに表示されます.
var currentIdentity = Csla.ApplicationContext.User.Identity as OEAIdentity;
currentIdentity.GetDataPermissionExpr(businessObjectId);

デバッグ検出、Csla.ApplicationContext.Userは、UnauthenticatedIdentityのインスタンスです.しかし、私たちはすでにログインしています.この属性はなぜ「無許可」なのでしょうか.ソースコードを確認すると、要求の処理の開始段階になるたびに、CSLAはこの属性をクライアントから送信されたユーザーIDに設定します.では、CSLAのこのプロパティのソースコードを見てみましょう.
private static IPrincipal _principal;
public static IPrincipal User
{
    get
    {
        IPrincipal current;
        if (HttpContext.Current != null)
            current = HttpContext.Current.User;
        else if (System.Windows.Application.Current != null)
        {
            if (_principal == null)
            {
                if (ApplicationContext.AuthenticationType != "Windows")
                    _principal = new Csla.Security.UnauthenticatedPrincipal();
                else
                    _principal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
            }
            current = _principal;
        }
        else
            current = Thread.CurrentPrincipal;
        return current;
    }
    set
    {
        if (HttpContext.Current != null)
            HttpContext.Current.User = value;
        else if (System.Windows.Application.Current != null)
            _principal = value;
        Thread.CurrentPrincipal = value;
    }
}

コードには、サービス側がWPFアプリケーションを使用している場合、現在のユーザーを静的フィールドで保存すると表示されます.つまり、サービス側のすべてのスレッドは最後のリクエストのユーザーしか取得できません.もちろん、マルチスレッドのサービスは提供できません.ここで、実は作者の1つの小さいBUGです:彼はWPFのプログラムを使うのがクライアントであるべきだと思って、だから直接静的な変数の中で保存します.しかし,我々のサービス側もWPFで実現しているため,各スレッドに独立したデータを使用できない.
このクラスはクライアントとサービス側で同時に使用されるため,変更はクライアントの正常な使用に影響を与えない.既存のコードを最小限に抑えるために、フィールドのコードを次のように変更しました.
[ThreadStatic]
private static IPrincipal __principalThreadSafe;
private static IPrincipal __principal;
private static IPrincipal _principal
{
    get
    {
        return _executionLocation == ExecutionLocations.Client ? __principal : __principalThreadSafe;
    }
    set
    {
        if (_executionLocation == ExecutionLocations.Client)
        {
            __principal = value;
        }
        else
        {
            __principalThreadSafe = value;
        }
    }
}

ここでは元のフィールドを属性に変更します!これを実装する場合は、クライアントで実行するか、一般的な静的フィールドを使用します.サービス側であれば、「ThreadStatic」とマークされたフィールドに変わります.このフィールドは、スレッドごとに独立した値を割り当てます.これにより、サービス側は、要求が処理される開始段階で対_principalが値を割り当てると、他のスレッドに影響を与えることなく、現在のスレッドに格納されます.
 
手動で開くスレッド
上の2つの問題はすでに解決しました:1、デフォルトはマルチスレッドを開いていません;2、複数スレッド対ApplicationContext.Userクラスが値を割り当てる場合、静的フィールドを使用すると値が競合します.
これで枕が高くなりますか?答えはいいえ!:)
これはWCFが要求を処理するためのスレッドのうち、ApplicationContext.Userプロパティの値は正しいです.しかし、個別のリクエストを処理する場合、より多くのスレッドを手動で開いてサービスする可能性があります.これらのスレッドのApplicationContext.UserフィールドはCSLAフレームワークに割り当てられていません.この場合、それを使用すると、Null ReferenceExceptionが表示されます.
我々が非同期処理を行う際のコードはいずれも微細なパッケージを経ているため,この場合の利点が現れる.我々の処理案は、手動で非同期実行を申請する方法の実装において、転送された非同期操作に「ラップ」を追加することである.例えば、次のAPIは、クライアントプログラムに非同期操作を呼び出すために使用され、当時はスレッドプールをカプセル化した単純な呼び出しであり、将来の拡張を容易にするためである(例えば、Taskに変更して実現することができる......).
public static void SafeInvoke(Action action)
{
    ThreadPool.QueueUserWorkItem(o => action());
}

次のような拡張方法を追加しました.
/// <summary>
///      wrapper   ,   action  ,               Principel。
///
///     :
///   ApplicationContext.User      ,
///            ,                    ,
///                    Principle       。
/// </summary>
/// <param name="action">
///      ApplicationContext.User,                 。
/// </param>
/// <returns></returns>
public static Action AsynPrincipleWrapper(this Action action)
{
    if (ApplicationContext.ExecutionLocation == ApplicationContext.ExecutionLocations.Client)
    {
        return action;
    }
    var principelNeed = ApplicationContext.User;
    return () =>
    {
        var oldPrincipel = ApplicationContext.User;
        if (oldPrincipel != principelNeed)
        {
            ApplicationContext.User = principelNeed;
        }
        try
        {
            action();
        }
        finally
        {
            if (oldPrincipel != principelNeed)
            {
                ApplicationContext.User = oldPrincipel;
            }
        }
    };
}

元のAPIは次のように変更されました.
public static void SafeInvoke(Action action)
{
    action = action.AsynPrincipleWrapper();
    ThreadPool.QueueUserWorkItem(o => action());
}
は、手動で開くスレッドと、開くスレッドと同じApplicationContextを使用することを実現する.User.
小結
本文は主にCSLAフレームワークのサービス端をマルチスレッドをサポートする方法を紹介した.CSLAフレームワークを使っている友人に役立つかもしれません.
次はGIX 4プロジェクトの例を適用し、これらの記事で述べた方法を具体的なプロジェクトでどのように適用するかを説明します.