当前位置:主页   - 电脑 - 网站开发 - ASP.Net
性能优化总结(五):CSLA服务端如何使用多线程的解决方案
来源:网络   作者:BloodyAngel   更新时间:2011-10-11
收藏此页】    【字号    】    【打印】    【关闭

  前篇说到了使用异步线程来实现数据的预加载,以提高系统性能。

  这样的操作一般是在客户端执行,用以减少用户的等待时间。客户端发送多次异步请求,到达服务端后,如果服务端不支持多线程处理操作,线性处理各个请求,必然导致客户端的异步请求变得没有意义。

  大家肯定会说,谁会把服务端设计成单线程的啊,那不是明显的错误吗?是的!但是我们的系统使用了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,所以已经被设计为单线程操作。在这里,我们使用装饰模式来构造一个新的类:

编缉推荐阅读以下文章

  • 性能优化总结(六):预加载、聚合SQL应用实例
  • 性能优化总结(四):预加载的设计
  • 性能优化总结(三):聚合SQL在GIX4中的应用
  • 性能优化总结(二):聚合SQL
  • 性能优化总结(一):前言

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 } 

编缉推荐阅读以下文章

  • 性能优化总结(六):预加载、聚合SQL应用实例
  • 性能优化总结(四):预加载的设计
  • 性能优化总结(三):聚合SQL在GIX4中的应用
  • 性能优化总结(二):聚合SQL
  • 性能优化总结(一):前言

  这样,在服务端接收到请求时,会自动开启多个线程来响应请求。同时,装饰模式的使用使得我们不需要对源代码进行任何更改。

  修改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 } 

编缉推荐阅读以下文章

  • 性能优化总结(六):预加载、聚合SQL应用实例
  • 性能优化总结(四):预加载的设计
  • 性能优化总结(三):聚合SQL在GIX4中的应用
  • 性能优化总结(二):聚合SQL
  • 性能优化总结(一):前言

  代码中显示,如果服务端使用的是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 } 

  这里把原来的字段变为了一个属性!实现它时,如果是在客户端,还是使用一个一般的静态字段。如果是在服务端时,就换成了一个标记了[ThreadStatic]的字段,该标记表示:这个字段会为每一个线程分配独立的值。这样,服务端在请求被处理的开始阶段对_principal赋值时,就存储在了当前线程中,而不会影响其它线程。

编缉推荐阅读以下文章

  • 性能优化总结(六):预加载、聚合SQL应用实例
  • 性能优化总结(四):预加载的设计
  • 性能优化总结(三):聚合SQL在GIX4中的应用
  • 性能优化总结(二):聚合SQL
  • 性能优化总结(一):前言

  手动开启的线程

  上面已经解决了两个问题: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项目中的实例,说明一下在具体项目中如何应用这几篇文章中提到的方法。

编缉推荐阅读以下文章

  • 性能优化总结(六):预加载、聚合SQL应用实例
  • 性能优化总结(四):预加载的设计
  • 性能优化总结(三):聚合SQL在GIX4中的应用
  • 性能优化总结(二):聚合SQL
  • 性能优化总结(一):前言
其它资源
来源声明

版权与免责声明
1、本站所发布的文章仅供技术交流参考,本站不主张将其做为决策的依据,浏览者可自愿选择采信与否,本站不对因采信这些信息所产生的任何问题负责。
2、本站部分文章来源于网络,其版权为原权利人所有。由于来源之故,有的文章未能获得作者姓名,署“未知”或“佚名”。对于这些文章,有知悉作者姓名的请告知本站,以便及时署名。如果作者要求删除,我们将予以删除。除此之外本站不再承担其它责任。
3、本站部分文章来源于本站原创,本站拥有所有权利。
4、如对本站发布的信息有异议,请联系我们,经本站确认后,将在三个工作日内做出修改或删除处理。
请参阅权责声明