当前位置: 首页 > news >正文

性能优化总结(五):CSLA服务端如何使用多线程的解决方案(转)

性能优化总结(五):CSLA服务端如何使用多线程的解决方案

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

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

    大家肯定会说,谁会把服务端设计成单线程的啊,那不是明显的错误吗?是的!但是我们的系统使用了CSLA来作为实现分布式的框架,而它的服务端程序却只能支持单线程……这个问题我们一直想解决,但是查过CSLA官方论坛,作者说由于GlobalContext和ClientContext的一些原因,暂时不支持多线程。火大,这还怎么用啊!无奈目前系统已经极大地依赖了这个框架,一时半会儿要想换一个新的,也不太现实。所以只好自己动手修改CSLA里面的代码了:

 

修改WCF通信类

    要修改为多线程的服务端,首先得从服务端的请求处理处入手。.NET3.5的CSLA框架使用WCF实现数据传输。它在服务器端使用这个类来接收:

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

可以看到,这个类已经被标注了InstanceContextMode.PerCall,所以这个类已经被设计为单线程操作。在这里,我们使用装饰模式来构造一个新的类:

view source
print ?
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)]
09public class MultiThreadsWCFPortal : IWcfPortal
10{
11    private WcfPortal _innerPortal = new WcfPortal();
12  
13    #region IWcfPortal Members
14  
15    public WcfResponse Create(CreateRequest request)
16    {
17        return this._innerPortal.Create(request);
18    }
19  
20    //...
21  
22    #endregion
23}

同时,我们需要把配置文件和类的实例化两处代码都替换:

app.config:

view source
print ?
1<services>
view source
print ?
1<!--Csla.Server.Hosts.WcfPortal-->
view source
print ?
1    <service name="OpenExpressApp.Server.WPFHost.MultiThreadsWCFPortal" behaviorConfiguration="returnFaults">
2        .....
3    </service>
4</services>

 

factory method:

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

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

 

修改ApplicationContext._principal字段

    按照上面的操作修改之后,已经在WCF级别上实现了多线程。但是当再次运行应用程序时,会抛出NullRefrenceException异常。代码出现在这里:

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

调试发现,Csla.ApplicationContext.User是一个UnauthenticatedIdentity的实例。可是我们已经登录了,这个属性为什么还是“未授权”呢?查看源代码,发现每次在处理请求的开始阶段,CSLA会设置这个属性为客户端传入的用户标识。那么我们来看这个属性在CSLA中的源代码:

view source
print ?
01private static IPrincipal _principal;
02public 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来实现的,所以就导致了无法为每个线程使用独立的数据。

这个类同时被客户端和服务端所使用,所以改动不能影响客户端的正常使用。为了最少地改动原有代码,我把字段的代码修改为:

view source
print ?
01[ThreadStatic]
02private static IPrincipal __principalThreadSafe;
03private static IPrincipal __principal;
04private 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赋值时,就存储在了当前线程中,而不会影响其它线程。

 

手动开启的线程

    上面已经解决了两个问题:1、默认没有打开多线程;2、多个线程对ApplicationContext.User类赋值时,使用静态字段导致值的冲突。

    这样就高枕无忧了吗?答案是不!:)

    这样只是保证了WCF用于处理请求的线程中,ApplicationContext.User属性的值是正确的。但是我们在处理一个单独的请求时,又很有可能手工打开更多的线程来为它服务。这些线程的ApplicationContext.User字段并没有被CSLA框架赋值,如果这时使用到它时,又会出现NullRefrenceException……

    由于我们进行异步处理时的代码都是经过一层细微的封装的,所以这时候好处就体现出来了。我们的处理方案是,在手工申请异步执行的方法实现中,为传入的异步操作加一层“包裹器”,例如下面这个API,它是用来给客户程序调用异步操作的,当时只是封装了线程池的简单调用,为的就是方便将来做扩展(例如我们可以改为Task来实现……)。

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

我们添加了一个扩展方法如下:

view source
print ?
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>
13public static Action AsynPrincipleWrapper(this Action action)
14{
15    if (ApplicationContext.ExecutionLocation == ApplicationContext.ExecutionLocations.Client)
16    {
17        return action;
18    }
19  
20    var principelNeed = ApplicationContext.User;
21  
22    return () =>
23    {
24        var oldPrincipel = ApplicationContext.User;
25        if (oldPrincipel != principelNeed)
26        {
27            ApplicationContext.User = principelNeed;
28        }
29  
30        try
31        {
32            action();
33        }
34        finally
35        {
36            if (oldPrincipel != principelNeed)
37            {
38                ApplicationContext.User = oldPrincipel;
39            }
40        }
41    };
42}

原来的API改为:

view source
print ?
1public static void SafeInvoke(Action action)
2{
3    action = action.AsynPrincipleWrapper();
4  
5    ThreadPool.QueueUserWorkItem(o => action());
6}
view source
print ?
1这样就实现了:手工打开的线程,使用和打开者线程相同的一个ApplicationContext.User。
view source
print ?
1  
view source
print ?
1<STRONG>小结</STRONG>
view source
print ?
1本文主要介绍了如何把CSLA框架的服务端打造为支持多线程。可能会对使用CSLA框架的朋友会有所帮助。
view source
print ?
1下一篇应用一个在GIX4项目中的实例,说明一下在具体项目中如何应用这几篇文章中提到的方法

转载于:https://www.cnblogs.com/dyheee/archive/2010/07/01/1769552.html

相关文章:

  • win7下使用matlab7.1,解决java错误提示。
  • 按时间自动关闭的弹出对话框
  • 在MTK上实现数独小游戏
  • 把javascript,vbscript中得数组传递给COM组件(or Activex)
  • 在C#中完成海量数据的批量插入和更新
  • js split()与John截取函数(复制)
  • photoshop保存背景透明图片办法
  • 软件配置项
  • php字符串变数组
  • 向现有表添加标识列(IDENTITY)
  • 轻松实现无刷新三级联动菜单[VS2005与AjaxPro]【转】
  • ArcSDE vs. Oracle Spatial 16
  • 论文选题系统
  • 使用Reflector查看闭包
  • 巧替换windows 7中的宋体 simsun.ttc
  • 【译】React性能工程(下) -- 深入研究React性能调试
  • 4. 路由到控制器 - Laravel从零开始教程
  • es6--symbol
  • HTTP中的ETag在移动客户端的应用
  • Javascript Math对象和Date对象常用方法详解
  • JSDuck 与 AngularJS 融合技巧
  • MySQL数据库运维之数据恢复
  • python3 使用 asyncio 代替线程
  • Solarized Scheme
  • SpiderData 2019年2月25日 DApp数据排行榜
  • vue 个人积累(使用工具,组件)
  • 纯 javascript 半自动式下滑一定高度,导航栏固定
  • 从0搭建SpringBoot的HelloWorld -- Java版本
  • 记录:CentOS7.2配置LNMP环境记录
  • 全栈开发——Linux
  • 使用Tinker来调试Laravel应用程序的数据以及使用Tinker一些总结
  • 想使用 MongoDB ,你应该了解这8个方面!
  • 在electron中实现跨域请求,无需更改服务器端设置
  • 怎么将电脑中的声音录制成WAV格式
  • 7行Python代码的人脸识别
  • ​七周四次课(5月9日)iptables filter表案例、iptables nat表应用
  • # Maven错误Error executing Maven
  • # 安徽锐锋科技IDMS系统简介
  • #WEB前端(HTML属性)
  • #使用清华镜像源 安装/更新 指定版本tensorflow
  • (0)Nginx 功能特性
  • (1)安装hadoop之虚拟机准备(配置IP与主机名)
  • (done) ROC曲线 和 AUC值 分别是什么?
  • (Note)C++中的继承方式
  • (附源码)spring boot智能服药提醒app 毕业设计 102151
  • (黑马C++)L06 重载与继承
  • (实战篇)如何缓存数据
  • (转)LINQ之路
  • (转)甲方乙方——赵民谈找工作
  • (转)拼包函数及网络封包的异常处理(含代码)
  • (最全解法)输入一个整数,输出该数二进制表示中1的个数。
  • .locked1、locked勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .Net Core 中间件验签
  • .NET DevOps 接入指南 | 1. GitLab 安装
  • .NET 中让 Task 支持带超时的异步等待