您的位置:首页 > 其它

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

2010-07-01 23:09 573 查看

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

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

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

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

修改WCF通信类

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

viewsource

print?

1
namespace
Csla.Server.Hosts
2
{
3
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerCall)]
4
[AspNetCompatibilityRequirements(RequirementsMode=AspNetCompatibilityRequirementsMode.Allowed)]
5
public
class
WcfPortal:IWcfPortal{}
6
}
可以看到,这个类已经被标注了InstanceContextMode.PerCall,所以这个类已经被设计为单线程操作。在这里,我们使用装饰模式来构造一个新的类:

viewsource

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)]
09
public
class
MultiThreadsWCFPortal:IWcfPortal
10
{
11
private
WcfPortal_innerPortal=
new
WcfPortal();
12
13
#regionIWcfPortalMembers
14
15
public
WcfResponseCreate(CreateRequestrequest)
16
{
17
return
this
._innerPortal.Create(request);
18
}
19
20
//...
21
22
#endregion
23
}
同时,我们需要把配置文件和类的实例化两处代码都替换:

app.config:

viewsource

print?

1
<
services
>
viewsource

print?

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

print?

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

viewsource

print?

1
private
static
TypeGetServerHostType()
2
{
3
return
typeof
(OpenExpressApp.Server.WPFHost.MultiThreadsWCFPortal);
4
//returntypeof(Csla.Server.Hosts.WcfPortal);
5
}
这样,在服务端接收到请求时,会自动开启多个线程来响应请求。同时,装饰模式的使用使得我们不需要对源代码进行任何更改。

修改ApplicationContext._principal字段

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

viewsource

print?

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

viewsource

print?

01
private
static
IPrincipal_principal;
02
public
static
IPrincipalUser
03
{
04
get
05
{
06
IPrincipalcurrent;
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来实现的,所以就导致了无法为每个线程使用独立的数据。

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

viewsource

print?

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赋值时,就存储在了当前线程中,而不会影响其它线程。

手动开启的线程

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

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

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

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

viewsource

print?

1
public
static
void
SafeInvoke(Actionaction)
2
{
3
ThreadPool.QueueUserWorkItem(o=>action());
4
}
我们添加了一个扩展方法如下:

viewsource

print?

01
///<summary>
02
///这里生成的wrapper会保证,在执行action前后,新开的线程和主线程都使用同一个Principel。
03
///
04
///解决问题:
05
///由于ApplicationContext.User是基于线程的,
06
///所以如果在同一次请求中,如果在服务端打开一个新的线程做一定的事情,
07
///这个新开的线程可能会和打开者使用不同的Principle而造成代码异常。
08
///</summary>
09
///<paramname="action">
10
///可能会使用ApplicationContext.User,并需要在服务端另开线程来执行的操作。
11
///</param>
12
///<returns></returns>
13
public
static
ActionAsynPrincipleWrapper(
this
Actionaction)
14
{
15
if
(ApplicationContext.ExecutionLocation==ApplicationContext.ExecutionLocations.Client)
16
{
17
return
action;
18
}
19
20
varprincipelNeed=ApplicationContext.User;
21
22
return
()=>
23
{
24
varoldPrincipel=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改为:

viewsource

print?

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

print?

1
这样就实现了:手工打开的线程,使用和打开者线程相同的一个ApplicationContext.User。
viewsource

print?

1
viewsource

print?

1
<STRONG>小结</STRONG>
viewsource

print?

1
本文主要介绍了如何把CSLA框架的服务端打造为支持多线程。可能会对使用CSLA框架的朋友会有所帮助。
viewsource

print?

1
下一篇应用一个在GIX4项目中的实例,说明一下在具体项目中如何应用这几篇文章中提到的方法
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: