程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 性能優化總結(五):CSLA服務端如何使用多線程的解決方案

性能優化總結(五):CSLA服務端如何使用多線程的解決方案

編輯:關於.NET

前篇說到了使用異步線程來實現數據的預加載,以提高系統性能。

這樣的操作一般是在客戶端執行,用以減少用戶的等待時間。客戶端發送多次異步請求,到達服務端後,如果服務端不支持多線程處理 操作,線性處理各個請求,必然導致客戶端的異步請求變得沒有意義。

大家肯定會說,誰會把服務端設計成單線程的啊,那不是明顯的錯誤嗎?是的!但是我們的系統使用了CSLA來作為實現分布式的框架, 而它的服務端程序卻只能支持單線程……這個問題我們一直想解決,但是查過CSLA官方論壇,作者說由於GlobalContext和ClientContext 的一些原因,暫時不支持多線程。火大,這還怎麼用啊!無奈目前系統已經極大地依賴了這個框架,一時半會兒要想換一個新的,也不太 現實。所以只好自己動手修改CSLA裡面的代碼了:

修改WCF通信類

要修改為多線程的服務端,首先得從服務端的請求處理處入手。.NET3.5的CSLA框架使用WCF實現數據傳輸。它在服務器端使用這個類來 接收:

1 namespace Csla.Server.Hosts
2 {
3     [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
4     [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
5     public class WcfPortal : IWcfPortal { }
6 }

可以看到,這個類沒有標注ConcurrencyMode = ConcurrencyMode.Multiple和UseSynchronizationContext = false,所以已經被設計 為單線程操作。在這裡,我們使用裝飾模式來構造一個新的類:

01 /// <summary>
02 /// 標記了ConcurrencyMode = ConcurrencyMode.Multiple
03 /// 來表示多線程進行 
04 /// </summary>
05 [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall,
06 ConcurrencyMode = ConcurrencyMode.Multiple,
07 UseSynchronizationContext = false)]
08 [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
09 public class MultiThreadsWCFPortal : IWcfPortal
10 {
11     private WcfPortal _innerPortal = new WcfPortal();
12     #region IWcfPortal Members
13     public WcfResponse Create(CreateRequest request)
14     {
15         return this._innerPortal.Create(request);
16     }
17     //...
18     #endregion
19 }

同時,我們需要把配置文件和類的實例化兩處代碼都替換:

app.config:

1 <services>

1 <!--Csla.Server.Hosts.WcfPortal-->

1     <service name="OpenExpressApp.Server.WPFHost.MultiThreadsWCFPortal"  behaviorConfiguration="returnFaults">
2 .....
3 </service>
4 </services>

factory method:

1 private static Type GetServerHostType()
2 {
3     return typeof(OpenExpressApp.Server.WPFHost.MultiThreadsWCFPortal);
4     //return typeof(Csla.Server.Hosts.WcfPortal);
5 }

這樣,在服務端接收到請求時,會自動開啟多個線程來響應請求。同時,裝飾模式的使用使得我們不需要對源代碼進行任何更改。

修改ApplicationContext._principal字段

按照上面的操作修改之後,已經在WCF級別上實現了多線程。但是當再次運行應用程序時,會拋出NullRefrenceException異常。代碼出 現在這裡:

1 var currentIdentity = Csla.ApplicationContext.User.Identity as OEAIdentity;
2 currentIdentity.GetDataPermissionExpr(businessObjectId);

調試發現,Csla.ApplicationContext.User是一個UnauthenticatedIdentity的實例。可是我們已經登錄了,這個屬性為什麼還是“未 授權”呢?查看源代碼,發現每次在處理請求的開始階段,CSLA會設置這個屬性為客戶端傳入的用戶標識。那麼我們來看這個屬性在CSLA 中的源代碼:

01 private static IPrincipal _principal;
02 public static IPrincipal User
03 {
04     get
05     {
06         IPrincipal current;
07         if (HttpContext.Current != null)
08             current = HttpContext.Current.User;
09         else if (System.Windows.Application.Current != null)
10         {
11             if (_principal == null)
12             {
13                 if (ApplicationContext.AuthenticationType != "Windows")
14                     _principal = new Csla.Security.UnauthenticatedPrincipal();
15                 else
16                     _principal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
17             }
18             current = _principal;
19         }
20         else
21             current = Thread.CurrentPrincipal;
22         return current;
23     }
24     set
25     {
26         if (HttpContext.Current != null)
27             HttpContext.Current.User = value;
28         else if (System.Windows.Application.Current != null)
29             _principal = value;
30         Thread.CurrentPrincipal = value;
31     }
32 }

代碼中顯示,如果服務端使用的是WPF應用程序時,就使用一個靜態字段保存當前的用戶。這就是說服務端的所有線程都只能獲取到最 後一個請求的用戶,當然就不能提供多線程的服務!這裡,其實是作者的一個小BUG:他認為使用WPF的程序應該就是客戶端,所以直接存 儲在靜態變量中。但是我們的服務端也是WPF來實現的,所以就導致了無法為每個線程使用獨立的數據。

這個類同時被客戶端和服務端所使用,所以改動不能影響客戶端的正常使用。為了最少地改動原有代碼,我把字段的代碼修改為:

01 [ThreadStatic]
02 private static IPrincipal __principalThreadSafe;
03 private static IPrincipal __principal;
04 private static IPrincipal _principal
05 {
06     get
07     {
08         return _executionLocation == ExecutionLocations.Client ? __principal :  __principalThreadSafe;
09     }
10     set
11     {
12         if (_executionLocation == ExecutionLocations.Client)
13         {
14             __principal = value;
15         }
16         else
17         {
18             __principalThreadSafe = value;
19         }
20     }
21 }

手動開啟的線程

上面已經解決了兩個問題:1、默認沒有打開多線程;2、多個線程對ApplicationContext.User類賦值時,使用靜態字段導致值的沖突 。

這樣就高枕無憂了嗎?答案是不!:)

這樣只是保證了WCF用於處理請求的線程中,ApplicationContext.User屬性的值是正確的。但是我們在處理一個單獨的請求時,又很有 可能手工打開更多的線程來為它服務。這些線程的ApplicationContext.User字段並沒有被CSLA框架賦值,如果這時使用到它時,又會出現 NullRefrenceException……

由於我們進行異步處理時的代碼都是經過一層細微的封裝的,所以這時候好處就體現出來了。我們的處理方案是,在手工申請異步執行 的方法實現中,為傳入的異步操作加一層“包裹器”,例如下面這個API,它是用來給客戶程序調用異步操作的,當時只是封裝了線程池的 簡單調用,為的就是方便將來做擴展(例如我們可以改為Task來實現……)。

1 public static void SafeInvoke(Action action)
2 {
3     ThreadPool.QueueUserWorkItem(o => action());
4 }

我們添加了一個擴展方法如下:

01 /// <summary>
02 /// 這裡生成的wrapper會保證,在執行action前後,新開的線程和主線程都使用同一個Principel。
03 /// 
04 /// 解決問題:
05 /// 由於ApplicationContext.User是基於線程的,
06 /// 所以如果在同一次請求中,如果在服務端打開一個新的線程做一定的事情,
07 /// 這個新開的線程可能會和打開者使用不同的Principle而造成代碼異常。
08 /// </summary>
09 /// <param name="action">
10 /// 可能會使用ApplicationContext.User,並需要在服務端另開線程來執行的操作。
11 /// </param>
12 /// <returns></returns>
13 public static Action AsynPrincipleWrapper(this Action action)
14 {
15     if (ApplicationContext.ExecutionLocation == ApplicationContext.ExecutionLocations.Client)
16     {
17         return action;
18     }
19     var principelNeed = ApplicationContext.User;
20     return () =>
21     {
22         var oldPrincipel = ApplicationContext.User;
23         if (oldPrincipel != principelNeed)
24         {
25             ApplicationContext.User = principelNeed;
26         }
27         try 
28         {
29             action();
30         }
31         finally 
32         {
33             if (oldPrincipel != principelNeed)
34             {
35                 ApplicationContext.User = oldPrincipel;
36             }
37         }
38     };
39 }

原來的API改為:

1 public static void SafeInvoke(Action action)
2 {
3     action = action.AsynPrincipleWrapper();
4     ThreadPool.QueueUserWorkItem(o => action());
5 }

這樣就實現了:手工打開的線程,使用和打開者線程相同的一個ApplicationContext.User。

小結

本文主要介紹了如何把CSLA框架的服務端打造為支持多線程。可能會對使用CSLA框架的朋友會有所幫助。

下一篇應用一個在GIX4項目中的實例,說明一下在具體項目中如何應用這幾篇文章中提到的方法。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved