浅析.Net 在 winform及wpf中涉及界面交互的多线程类的封装

我们在开发客户端程序时,经常要用到多线程

而我们知道 .Net 中从线程安全的考虑,对控件改变的调用要通过Invoke beginvoke 来进行,这就涉及怎么进行工作线程与界面的互操作。

本文以一个简单的应用为例,探讨多线程的封装性,我们的主要目的是让一个类能同时适wpfwinform对多线程的要求

现在假定程序里,有一个多线程的业务处理逻辑,如下:

// ……初始化等
Thread work =  new Thread( new ThreadStart(run));
work.Start();
// ……其他代码

void run (){

// 这里有一部分要改变界面显示的代码

 

在最直观的设计中,我们是这样考虑的:

1.声明一个委托(为了从简,这里不考虑有各种参数的情况:

private  void foo();

2.将用户界面交互的代码装在一个单独的函数中:

private  void Foo(){
// 改变界面显示
}

3.在线程中(这里先以winform为例) InvokeBeginInvoke调用 这个函数

为了从简,我们只考虑Invoke

 

void run(){
// ….
this.Invoke( new foo(Foo));
// ….
}

现在,我们的下一步工作是要封装这个Work类,使他在其他的winform程序中也可以方便的调用。这样,我们主要有两部分工作要做:

 a.工作线程的提取

 b.界面逻辑的分割

最直接的方法是这样:

 

delegate  void foo();
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

 

FooFuction(){
this.Invoke( new Foo(fooFunction));
}
fooFunction(){
// 界面处理程序
}

于是,每次复用这个逻辑,我们都要反复的自己写这个Invoke ,很麻烦,于是考虑进一步的改进,即,将当前Control作为一个成员封装至Work类中,即:

 

delegate  void foo();
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程序兼容,我得自己在程序中写对应wpfInvoke

2.运行时要依靠Control类,即我要正常使用他,还得在wpf程序中添加winformDLL才可以

因此,我们要进行如下工作,以使我们的Work类看着很完美

1.脱离Control类的依赖

2.能同时完美的支持winform wpf用户程序;

显然,这里面最重要的就是要如何处理这段代码 :

 

// 这里有一部分要改变界面显示的代码
if(Foo!= null)
     if(Ownner!= null)
        Ownner.Invoke(Foo);
     else 
        Foo();

考虑到我们对Control的引用 只是为了调用它的Invoke ,于是,可以将Invoke提一个接口

interface  IWorkHost{
void Invoke ( delegate method);
public  object Parent;
}

这样,我们只需将Work类中的Owner Control变为IWorkHost,其他代码不用改变,剩下的工作就好办了:

我们可以把对windows.forms 引用从Work所在的工程移掉,然后,加一个DLL工程WinformHost ,引用 Work类所在工程

声明一个类WinformHost来实现IWorkHost

class 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

class WPFHost:IWorkHost{
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 =  new Work( new WinformHost( this));
Work.Foo+=  new foo(FooFunction);
Work.Begin()

 

当在 wpf程序中使用 work类时,代码变成:

 

Work =  new Work( new WPFHost( this.dispatcher));
Work.Foo+=  new foo(FooFunction);
Work.Begin()

总结:

我们在这个文章里依次做了如下的工作:

1.将一个多线程的逻辑加到winform中

2.将这个多线程封装到单独的类

3.改进这个逻辑使他同时适应winform和wpf

这里用到到模式,应该是适配器模式,其实这个应用场景和很多复用封装的场景相类似,只不过多线程在 winform和wpf中是经常讨论的问题,用这个问题来解释这种封装,我觉得会好一些。

文笔不好,见谅。

你可能感兴趣的:(WinForm)