您的位置:首页 > 运维架构

应用AOP简化WinForm的异步操作——PostSharp实现

2011-11-19 10:49 375 查看

起因从事WinForm的园友们一定经历过:当程序需要执行一个耗时的操作时,窗体进入假死状态,然后标题栏显示一个令用户绝望的“未响应”。不明真相的用户此时可能认为程序已死,强行关闭重启程序,然后重复这个噩梦。而通常此时程序实际上已经完成了一部分业务,造成数据丢失或者产生的结果与预期不一致。针对这个case,有很多方法来解决:Thread/ThreadPool/Control.Invoke/BackgroundWorker...下面以BackgroundWorker举例:假设程序现在要执行一个耗时的操作,为了不让用户肆意猛击界面,弹出一个带有loading动画的对话框block住主界面。我们把这个对话框命名为BlockDialog效果如下:实现的代码可能如下,DoWork方法用于执行业务,RunWorkerCompleted用于绑定数据、刷新界面:
var worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(DoWork);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(RunWorkerCompleted);
worker.RunWorkerAsync();
dialog = new BlockDialog();
dialog.ShowDialog(this);
反复写这样的代码让人感到厌倦,而且当一个复杂的界面出现大篇类似的代码,会让业务逻辑淹没其中难以厘清。所以,我们想到了AOP,通过attribute标记方法。以下使用大名鼎鼎的PostSharp作为基础工具二次开发。尝试我期望的代码风格是这样的:
[WorkThread]
void DoBusinessWork(){
LoadData();
Binding();
}

[GuiThread]
void Binding(){

}
现在开始:定制拦截方法的拦截器,继承自PostSharp的 MethodInterceptionAspect类
//业务线程
public class WorkThreadAttribute : MethodInterceptionAspect
{
private IBlockDialog blockForm;

public override void OnInvoke(MethodInterceptionArgs args)
{
var worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.RunWorkerAsync(args);
blockForm = args.Instance as IBlockDialog;
if (blockForm != null)
{
blockForm.Block();
}
}

void worker_DoWork(object sender, DoWorkEventArgs e)
{
var args = e.Argument as MethodInterceptionArgs;
args.Invoke(args.Arguments);
}

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

}
//GUI线程public class GuiThreadAttribute : MethodInterceptionAspect{public override void OnInvoke(MethodInterceptionArgs args){var main = (Form)args.Instance;if (main.InvokeRequired){main.BeginInvoke(new Action(args.Proceed));}else{args.Proceed();}}}
其中定义了一个接口IBlockDialog,为的是调用者可以自定义Block对话框
public interface IBlockDialog{void Block();void UnBlock();}
执行异步操作的Form需要实现此接口,它需要具有Block/Unblock的行为。简单地实现,它只需要组合BlockDialog,Show/Close对话框即可。
public class BaseForm : Form, IBlockDialog{private BlockDialog blockDialog;protected     BlockDialog BlockDialog{get{return blockDialog ?? (blockDialog = new BlockDialog());}}#region Implementation of IBlockDialogpublic void Block(){BlockDialog.ShowDialog(this);}public void UnBlock(){BlockDialog.Close();}public void ShowProcess(int percentage){BlockDialog.ShowProcess(percentage);}#endregion}
以上基本已经实现本文的目标,它能满足常用的下载数据并绑定展示的需求。
增强现在再提一个需求,我们经常需要上传或导入数据,希望在BlockDialog上加一个进度条。这时我们可以用到BackgroundWorker ReportProgress的特性。问题是:如何让拦截器知道业务线程执行的进度?暂且不论拦截器是如何得知的,首先,业务线程自己先要知道自己执行了百分之几,并且,它还得讲出来,不能闷闷唧唧的执行。为此,1、我们定义一个Reporter接口,它要求业务线程实时的set进度值,同时可能让拦截器get到这个进度值。
public interface IProcessReporter{int ProcessPercentage { get; set; }}
2、下面对BaseForm扩展,实现IProcessReporter
public class ProcessForm : BaseForm , IProcessReporter{
    public int ProcessPercentage{get;set;}    
  [WorkThread]  private void DoWork()  {     //循环     //报告进度     //ProcessPercentage = ?%;  }}
3、让拦截器监听IProcessReporter.ProcessPercentage的变化
怎么监听呢,用Timer吗?
可以,但有延时。
那用Observer吧!
Interceptor:在ProcessObsever处登记它启用的BackgroundWorker和要监听的Form:IProcessReporterProcessForm:进度变化时在ProcessObsever处更新ProcessObsever:Form执行业务进度发生变化时,通知要监听它的BackgroundWorker3.1 ProcessObserver的实现
public  class ProcessObserver{private static readonly Dictionary<BackgroundWorker, IProcessReporter> pairs = new Dictionary<BackgroundWorker, IProcessReporter>();private static readonly Dictionary<IProcessReporter,int> process = new Dictionary<IProcessReporter, int>();private static readonly ProcessObserver instance = new ProcessObserver();private ProcessObserver(){}public static ProcessObserver Instance{get{return instance;}}public  void Add(BackgroundWorker worker, IProcessReporter reporter){if (!pairs.ContainsKey(worker)){pairs.Add(worker, reporter);if (process.ContainsKey(reporter)){worker.ReportProgress(process[reporter]);}}}public  void Remove(BackgroundWorker worker){if(!pairs.ContainsKey(worker)) return;if (process.ContainsKey(pairs[worker])){process.Remove(pairs[worker]);}pairs.Remove(worker);}public  void Update(IProcessReporter reporter){if(process.ContainsKey(reporter)) process[reporter] = reporter.ProcessPercentage;else{process.Add(reporter, reporter.ProcessPercentage);}foreach (var pair in pairs){if (pair.Value == reporter){ pair.Key.ReportProgress(reporter.ProcessPercentage);//找到对应的BackgroundWorker,刷新进度}}}}
3.2 修改拦截器 WorkThreadAttribute
public override void OnInvoke(MethodInterceptionArgs args){  var worker = new BackgroundWorker();worker.DoWork += new DoWorkEventHandler(worker_DoWork);worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
       if (args.Instance is IProcessReporter)    {       processReporter = args.Instance as IProcessReporter;      ProcessObserver.Instance.Add(worker, processReporter);      worker.WorkerReportsProgress = true;      worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);    }  
        worker.RunWorkerAsync(args);blockForm = args.Instance as IBlockDialog;if (blockForm != null){blockForm.Block();}}
3.3 修改ProcessForm 对 IProcessReporter的实现,进度发生变化时更新到Observer
private int percentage;public int ProcessPercentage{get{ return percentage; }set {         if (percentage != value){  ProcessObserver.Instance.Update(this);}percentage = value;}}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: