浅析.Net 在 winform及wpf中涉及界面交互的多线程类的封装
我们在开发客户端程序时,经常要用到多线程
而我们知道 , .Net 中从线程安全的考虑,对控件改变的调用要通过Invoke, beginvoke 来进行,这就涉及怎么进行工作线程与界面的互操作。
本文以一个简单的应用为例,探讨多线程的封装性,我们的主要目的是让一个类能同时适wpf及winform对多线程的要求
现在假定程序里,有一个多线程的业务处理逻辑,如下:
Thread work = new Thread(new ThreadStart(run));
work.Start();
//……其他代码
void run (){
//这里有一部分要改变界面显示的代码
}
在最直观的设计中,我们是这样考虑的:
1.声明一个委托(为了从简,这里不考虑有各种参数的情况:
2.将用户界面交互的代码装在一个单独的函数中:
//改变界面显示
}
3.在线程中(这里先以winform为例) 用Invoke或BeginInvoke调用 这个函数
为了从简,我们只考虑Invoke
//….
this.Invoke(new foo(Foo));
//….
}
现在,我们的下一步工作是要封装这个Work类,使他在其他的winform程序中也可以方便的调用。这样,我们主要有两部分工作要做:
a.工作线程的提取
b.界面逻辑的分割
最直接的方法是这样:
class Work{
private Thread work;
public Work(){
work= new Thread(new ThreadStart(run);
}
public Begin(){
work.Start();
}
public event foo Foo;
void run (){
//这里有一部分要改变界面显示的代码
if(Foo!=null)
Foo();
}
}
这样,界面上的代码变成了这个样子:
Work = new Work(this);
Work.Foo+= new foo(FooFunction);
Work.Begin()
到此,我们已经将多线程逻辑与界面逻辑分开了,但是现在有一个问题,就是我 要在FooFuction中调用Invoke
即
this.Invoke(new Foo(fooFunction));
}
fooFunction(){
//界面处理程序
}
于是,每次复用这个逻辑,我们都要反复的自己写这个Invoke ,很麻烦,于是考虑进一步的改进,即,将当前Control作为一个成员封装至Work类中,即:
class Work{
private Control Owner ;
private Thread work;
public Work(Control owner){
work= new Thread(new ThreadStart(run);
Owner=owner;
}
public Begin(){
work.Start();
}
public event foo Foo;
void run (){
//这里有一部分要改变界面显示的代码
if(Foo!=null)
if(Ownner!=null)
Ownner.Invoke(Foo);
else
Foo();
}
}
这样,界面 上的代码就要整洁多了,现在我们不用在界面中调用 Invoke了:
Work = new Work();
Work.Foo+= new foo(FooFunction);
Work.Begin()
//other code ...
FooFuction(){
//更新界面
}
现在,我们已经完成了一个多线程类的封闭,它有很好的封闭性和可读性,但是,新的问题来了,即,我要把它移植到 wpf程序中,本来很完美的工作类,现在他面临如下问题:
1.不与wpf程序兼容,我得自己在程序中写对应wpf的Invoke
2.运行时要依靠Control类,即我要正常使用他,还得在wpf程序中添加winform的DLL才可以
因此,我们要进行如下工作,以使我们的Work类看着很完美
1.脱离Control类的依赖
2.能同时完美的支持winform 和wpf用户程序;
显然,这里面最重要的就是要如何处理这段代码 :
if(Foo!=null)
if(Ownner!=null)
Ownner.Invoke(Foo);
else
Foo();
考虑到我们对Control的引用 只是为了调用它的Invoke ,于是,可以将Invoke提一个接口 :
void Invoke (delegate method);
public object Parent;
}
这样,我们只需将Work类中的Owner 从Control变为IWorkHost,其他代码不用改变,剩下的工作就好办了:
我们可以把对windows.forms 引用从Work所在的工程移掉,然后,加一个DLL工程WinformHost ,引用 Work类所在工程
声明一个类WinformHost来实现IWorkHost
private Control parent;
public object Parent{get{return parent;}}
public WinformHost(Control parent){
parent = parent;
}
public void Invoke (delegate method){
parent.Invoke(method);
}
类似的,增加用于WPF的WPFWorkHost
private Dispatcher parent;
public object Parent{get{return parent;}}
public WPFHost(Control parent){
parent = parent;
}
public void Invoke (delegate method){
parent.Invoke(method);
}
这样,当在winform中使用Work类时,代码变成:
Work.Foo+= new foo(FooFunction);
Work.Begin()
当在 wpf程序中使用 work类时,代码变成:
Work.Foo+= new foo(FooFunction);
Work.Begin()
总结:
我们在这个文章里依次做了如下的工作:
1.将一个多线程的逻辑加到winform中
2.将这个多线程封装到单独的类
3.改进这个逻辑使他同时适应winform和wpf
这里用到到模式,应该是适配器模式,其实这个应用场景和很多复用封装的场景相类似,只不过多线程在 winform和wpf中是经常讨论的问题,用这个问题来解释这种封装,我觉得会好一些。
文笔不好,见谅。