这三个元素,Task代表着异步多线程,Lazy<T>代表着延迟初始化,INotifyPropertyChanged则代表着属性改变通知(多用于MVVM模式中)。那么如果把这三个元素组合起来就是一个异步延迟初始化的INotifyPropertyChanged属性。
实现起来是这样,因为Task和Lazy<T>只针对方法,而INotifyPropertyChanged只针对属性。所以异步延迟初始化的INotifyPropertyChanged属性还需要一个属性的初始化值,整个过程很简单。当第一次尝试获取属性值之后,初始值会被立刻返回,接着Task运行,注意此时无论多少个线程同时获取属性值,只有一个Task会运行(这是Lazy<T>的功劳),最终等这个Task运行结束后,属性值改变,通过INotifyPropertyChanged接口的执行来反馈给需要属性地方(比如界面上的数据绑定)。
这里我先对其他两两组合的情况讨论一下,当然相比把这三个一起组合起来,两两组合实现起来更简单些:
Task | Lazy<T> | 可以参考.NET Parallel Programming博客中的AsyncLazy<T>实现。 |
INotifyPropertyChanged | Task | 和没有TPL时的原始多线程操作一样,只不过需要注意操作结束后要在当前SynchronizationContext中执行属性的设置操作(因为属性改变会诱发UI改变,而UI改变必须在UI线程内)。 这里推荐用C# 5.0的async/await。 |
Lazy<T> | INotifyPropertyChanged | 没有太大意义,因为Lazy<T>的值是不可改变的。如果界面绑定的话,直接绑定Lazy<T>的Value属性。 |
下面讲我们这个三个组合的,运行示例程序可以看到,起初界面绑定的都是初始值(此时背后的Task已经开始运行),同时整个属性还提供是否加载完毕和有错误的信息(这些属性也是INotifyPropertyChanged执行的),在最下方有两个按钮又绑定了同样的属性值,但实际上异步延迟初始化只进行一次:
几秒后,Task运行完毕,界面发生了变化,属性1返回结果,属性2抛出异常,界面上会显示异常信息同时属性2的值保持初始值:
整个过程界面都是响应的,也就是说操作是异步的。
属性的类型名称是AsyncProperty,是在AsyncLazy<T>的基础上,加入了初始值和INotifyPropertyChanged的支持:
不过AsyncProperty<T>没有像AsyncLazy<T>那样以Lazy<Task<T>>为父类,而是把Lazy<Task<T>>作为一个字段,这样在初始化时更好被处理。用户可以通过Func<T>或者返回Task的Func<Task<T>>来初始化AsyncProperty类型。
AsyncValue属性是AsyncProperty的值,当第一次获取AsyncValue属性,它会立刻返回初始值,接着运行背后Task,当Task结束后,AsyncValue的属性会再次改变。
Error属性是错误信息,当Task中的代码发生异常后,Error会返回异常也就是Exception的Message属性。
HasValue和HasError代表是否又值和是否有错误。当HasError为True后HasValue也会是True,这里HasValue有点像是否完成初始化的意思。
(上述属性全是执行INotifyPropertyChanged接口)
AsyncProperty的使用就是这样很简单,那么示例程序中的ViewModel就是这样初始化AsyncProperty的:
public class ViewModel { public AsyncProperty<string> Data1 { get; private set; } public AsyncProperty<int> Data2 { get; private set; } public ViewModel() { //注意是返回Task的委托,而不是直接运行的Task var task1 = new Func<Task<string>>(() => Task.Run(() => { Task.Delay(3000).Wait(); return "Mgen"; })); var task2 = new Func<Task<int>>(() => Task.Run(() => { Task.Delay(2000).Wait(); throw new Exception("测试错误"); return 7; })); //初始化AsyncProperty,参数一是返回Task的委托,参数二是属性的初始值 Data1 = new AsyncProperty<string>(task1, "None"); Data2 = new AsyncProperty<int>(task2, -1); } }
OK,最后在界面上就直接绑定相应的属性就可以了。这里就不需要在贴代码了。读者自行参考示例程序和源代码。
源代码下载
mgen_lazyinpc.zip
源代码环境:Microsoft Visual Studio Express 2012 for Windows Desktop