回首征途
在上一篇《应用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
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:
[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