在最近的项目中,我封装一个异步执行程序的组件。这个组件的功能就是在执行一些耗时的后台操作(比如连数据库读取数据,或者后台进行一些统计计算)的时候不阻塞UI,再弹一个转圈圈的动画告诉用户正在执行后台操作。方法包含三个参数,其中前两个是委托类型,分别是耗时操作的doWork和操作完成后的回调callBack。第三个参数就是弹出的等待动画旁边的提示消息,类似于“正在查找数据,请稍后。。。"之类的文本,由调用方设定。方法代码大概长下面这个样子:
public static void Run(Action doWorker, Action callBack, string message) { //todo:method body }
因为这个组件我设计成不依赖于任何其他窗体,也就是说这个组件是一个独立的window,而不是作为一个控件插入到其他窗体。在其他任何window里调用都是直接调用这个静态方法即可在调用的window前弹出一个等待窗口,并且调用的地方也没有阻塞。那么问题来了,在这个方法里我怎么才知道是哪个window或者Control在调用呢?我采用的方法是使用doWorker的Target属性来获取当前调用的window,把它赋给组件的Owner,再把组件的启动位置设为Owner的中央即可。代码如下:
this.Owner = doWorker.Target as Window;if(this.Owner == null){ this.Owner = Window.GetWindow(doWorker.Target as Control);
} this.Show();
嗯,大功告成。run起来很顺畅,直到一段代码的出现。那是下面这样一段代码:
var orderTable = grd_Order.DataSource as DataTable; if(orderTable == null) return; MsgWindow.Run(()=>{ DbHepler.Update(orderTable); },null,"正在保存数据,请稍后!);
MsgWindow正是我封装的组件的类名。不知道为什么,这段代码调用时钟出错,异常出在Run方法中的这句代码:this.Owner = Window.GetWindow(Traget as Control);
因为调用组件的也许可能不是Window而是一个UserControl,而这句代码正是针对调用方是UserControl的。报的异常是说Window.GetWindow的参数不能为null,也就是说doWorker.Target as Control失败了,doWorker.Target根本不是一个Window,也不是一个Control。这怎么可能?!!代码明明就是写在一个自定义的Control。而且这个Control中还有另外一处也是调用的Run方法,参数都差不多,那里就没问题,运行得很顺畅。为什么就这个调用失败了呢?
经过跟踪调试,发现原来问题出在调用的地方。调的地方传第一个参数doWorker的时候,使用了闭包。在这个匿名方法中引用了外部变量orderTable。我想起之前看过的关于C#闭包的实现原理,其实是将引用的外部对象包装在一个匿名的类里了,引用的外部对象作为这个匿名类的一个成员。在这里,编译的时候C#编译器默默的为orderTable变量生成了一个匿名的类,orderTable作为这个匿名类的成员而保存了下来。要不是这么处理的话,还没来得及执行匿名方法,orderTable就已经超出了作用域而不可访问了。
找到问题根源后,解决方案也就呼之欲出了。其实这里根本用不着闭包,将orderTable放在匿名方法里面即可解决。如下:
if(orderTable == null) return; MsgWindow.Run(()=>{ var orderTable = grd_Order.DataSource as DataTable; DbHepler.Update(orderTable); },null,"正在保存数据,请稍后!);