• 应用AOP简化WinForm的异步操作——EntLib PIAB实现


    回首征途

    在上一篇《应用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
    
    

     

    作者:CnSharp.com
    本文版权归CnSharp.com和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    PAT 解题报告 1009. Product of Polynomials (25)
    PAT 解题报告 1007. Maximum Subsequence Sum (25)
    PAT 解题报告 1003. Emergency (25)
    PAT 解题报告 1004. Counting Leaves (30)
    【转】DataSource高级应用
    tomcat下jndi配置
    java中DriverManager跟DataSource获取getConnection有什么不同?
    理解JDBC和JNDI
    JDBC
    Dive into python 实例学python (2) —— 自省,apihelper
  • 原文地址:https://www.cnblogs.com/cnsharp/p/2271297.html
Copyright © 2020-2023  润新知