回首征途

 

在上一篇《应用AOP简化WINFORM的异步操作——PostSharp实现》中,实现了通过AOP的方式隔离BackgroundWorker的调用。

正如有朋友不倾向PostSharp的编译时代码织入方式,我也没在日常项目中使用过PostSharp。

虽然问题可能不大,弃用它也只是重新编译一遍。

但最近尝试Enterprise Library PIAB模块来实现相同的功能,还是发现了一些细节问题。

 

一鼓作气

 

与PostSharp不同,PIAB是以动态代理的方式来实现的。那么我们不能直接沿用Form中的代码,需要添加一个代理类来实现WorkThread。好吧,那么我们顺便引入MVP模式,通过Presenter类来作代理。

预想中的代码:

Model:

public class ArticleModel
{
public int Id { get; set; }

public string Title { get; set; }

public static List<ArticleModel> GetAll()
{
//todo
}
}

View:

public interface IArticleView
{
  List<ArticleModel> Articles { set; }
}
public class ArticleForm : IArticleView
{
  private readonly ArticlePresenter presenter;
  public List<ArticleModel> Articles
{
set
{
//todo:binding
}
}
}


Presenter:

public class ArticlePresenter 
{

private readonly IArticleView view;

private List<ArticleModel> articles;


public ArticlePresenter(IArticleView view)
{
this.view = view;
}

[WorkThread]
public void Download()
{
  //todo:loading data
           Binding();
}
         [GuiThread]
private void Binding()
{
view.Articles = articles;
}
}
  


 然后基于PIAB重新实现WorkThreadAttribute&GuiThreadAttribute

 以WorkThread为例,先要创建一个CallHandler

View Code
public class WorkThreadHandler : ICallHandler
{
#region Constants and Fields

private readonly bool reportProgress;

private IBlockDialog blockForm;

private BackgroundWorker worker;

private IMethodReturn methodReturn;


#endregion

#region Constructors and Destructors

public WorkThreadHandler(bool reportProgress)
{
this.reportProgress = reportProgress;
}


#endregion

#region Public Properties

public int Order { get; set; }

#endregion

#region Public Methods

public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
if (input == null)
{
throw new ArgumentNullException("input");
}
if (getNext == null)
{
throw new ArgumentNullException("getNext");
}

this.worker = new BackgroundWorker();
this.worker.DoWork += this.worker_DoWork;
this.worker.RunWorkerCompleted += this.worker_RunWorkerCompleted;
if (this.reportProgress)
{
this.worker.WorkerReportsProgress = true;
this.worker.ProgressChanged += this.worker_ProgressChanged;
}
this.worker.RunWorkerAsync(new object[] { input, getNext });

this.blockForm = input.Target as IBlockDialog;
if (this.blockForm != null)
{
this.blockForm.Handler = this;
this.blockForm.Block();
}

while (worker.IsBusy)
{
Thread.Sleep(500);
}
return methodReturn;
}

#endregion

#region Methods

private void worker_DoWork(object sender, DoWorkEventArgs e)
{
var args = e.Argument as object[];
var input = args[0] as IMethodInvocation;
var getNext = args[1] as GetNextHandlerDelegate;
//调用方法实现
methodReturn = getNext()(input, getNext);
}

private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.blockForm.ShowProcess(e.ProgressPercentage);
}

private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (this.blockForm != null)
{
this.blockForm.UnBlock();
}
}

#endregion
}

 实现Attribute:

View Code
[AttributeUsage(AttributeTargets.Method)]  
public class WorkThreadAttribute : HandlerAttribute
{
private bool reportProgress;
private int order;

public WorkThreadAttribute(bool reportProgress)
{
this.reportProgress = reportProgress;
}

public WorkThreadAttribute(bool reportProgress, int order)
: this(reportProgress)
{
this.order = order;
}
public WorkThreadAttribute() : this(false)
{
order = 1;
}


#region Overrides of HandlerAttribute

public override ICallHandler CreateHandler(IUnityContainer container)
{
var handler = new WorkThreadHandler(reportProgress){Order = order};
return handler;
}

#endregion
}
  


 GuiThreadHandler&GuiThreadAttribute如法炮制,做好Form,可以开始调试了!

 

山重水复

 

预料中的情况发生,甫一运行即遇上‘Cross-thread operation not valid’的异常。

是下面一段的Binding方法调用产生的,WorkThread试图访问UI:

 [WorkThread]
public void Download()
{
	//todo:loading data
     Binding();
}
[GuiThread]
private  void Binding()
{
  view.Articles = articles;
}

 为什么这个代码在PostSharp环境下能正常运行呢,因为PostSharp在编译时织入了它的拦截代码,客观上起到了GuiThread与WorkThread隔离的作用。

尝试了许多方法来规避这个问题,未果。

最终将目光聚焦到BackgroundWorker本身上。

通常情况下,我们通过BackgroundWorker.DoWork事件来加载数据,RunWorkerCompleted事件来展示数据。RunWorkerCompleted事件是在窗体主线程上实现的。

目前,我在RunWorkerCompleted事件上,只做了关闭Block Dialog的操作,何不同时做数据绑定呢!

private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
	if (this.blockForm != null)
	{
		this.blockForm.UnBlock();
	}
}


旧城改造
插叙:在完善代码之前,由于对上一篇引入的观察者不爽,先来修理一下。观察者模式常用于一对多的关系中,但在此例中BlockDialog和WorkHandler是一对一的关系,可以不用观察者,改为直接引用:
public interface IBlockDialog
{
IWorkThreadHandler Handler { get; set; }
void Block();

void UnBlock();

void ShowProcess(int percentage);

void ShowStatus(string status);
}

public interface IWorkThreadHandler
{
void Invoke(MethodInterceptionArgs args,bool reportProgress);

void ReportProgress(int percentage);
}


 其中Handler为新增属性,用于BlockDialog to WorkHandler的映射。 

IWorkThreadHandler为新增接口,通过ReportProgress方法向BackgroundWorker报告进程。


柳暗花明

现在来重新设计接口与Presenter的基类

IBlockView:Block Dialog界面的抽象
IBlockDialog:Presenter与Block Dialog交互的抽象,具有IBlockView的所有行为,WorkHandler需要通过Presenter来操控Block Dialog的弹出、关闭、进度条展示。
IAsyncPresenter:继承IBlockDialog,同时包含一个IBlockView。
AsyncPresenterBase:继承MarshalByRefObject,用于PIAB拦截。同时实现IAsyncPresenter。

回到数据绑定的问题上来,当前已经在BackgroundWorker.RunWorkerCompleted事件中调用了IBlockDialog.UnBlock,即关闭Block Dialog。
那么在IBlockDialog.UnBlock的实现代码中做数据绑定即可。
为AsyncPresenterBase基类添加虚方法:
protected virtual void Binding()
{
}
Block Dialog关闭时,调用Binding方法。
public void UnBlock()
{
	this.BlockView.UnBlock();
	this.Binding();
}
所有派生类重写Binding方法即可。
最终ArticlePresenter如下:
[WorkThread]
public void Download()
{
articles = ArticleModel.GetAll();
Thread.Sleep(3000);
}


protected override void Binding()
{
view.Articles = articles;
}

  1、删除GuiThreadAttribute

  2、重写Binding方法,WorkThread不直接调用Binding方法

 
共享成果
Demo:https://files.cnblogs.com/cnsharp/WinForm.AOP.PIAB.7z

 

posted on 2011-12-02 00:48  CnSharp Studio  阅读(2000)  评论(4编辑  收藏  举报