应用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;}}
相关文章推荐
- 应用AOP简化WinForm的异步操作——EntLib PIAB实现
- 应用AOP简化WinForm的异步操作——Spring.NET实现
- [置顶] Redis应用3-基于Redis消息队列实现的异步操作
- 一步一步教你使用AgileEAS.NET基础类库进行应用开发-WinForm应用篇-复杂业务的实现(商品入库)-附案例操作视频
- 一个通过BackgroundWorker实现WinForm异步操作的例子
- 一步一步教你使用AgileEAS.NET基础类库进行应用开发-WinForm应用篇-复杂业务的实现(商品入库)-附案例操作视
- C# BackgroundWorker实现WinForm异步操作的例子
- C# BackgroundWorker实现WinForm异步操作的例子
- C# BackgroundWorker实现WinForm异步操作的例子
- C# BackgroundWorker实现WinForm异步操作的例子
- [ZZ]一个通过BackgroundWorker实现WinForm异步操作的例子
- C# BackgroundWorker实现WinForm异步操作的例子
- [原创]一个通过BackgroundWorker实现WinForm异步操作的例子
- [导入][原创]一个通过BackgroundWorker实现WinForm异步操作的例子
- 一个通过BackgroundWorker实现WinForm异步操作的例子
- 使用ASM操作Java字节码,实现AOP原理
- iOS实现多个异步线程同步的操作
- 顺序存储操作的实现和线性表及其应用
- android中两种实现异步操作的方法,Handler和asynctask
- C#实现异步操作