何时在 WPF 中使用 Async 及 Reactive Extensions

  • Jonathan Allen
  • 邵思华

2013 年 3 月 28 日

话题:.NET语言 & 开发

Ian Griffiths 发布了包含六部分的一系列文章,讨论了何时应该、及何时不应在 WPF 中使用.NET 4.5 中的 async 特性。这个系列始于一篇名为“Too Much, Too Fast with WPF and Async”的贴子。

有了 async 这件神器,似乎有人就想在应用程序中到处使用,并时时刻刻调用它。不幸的是,如果每个 batch 的大小(即 async 调用之间的时间差)小于创建 Task 对象及对应的上下文切换的消耗,这种情况下 async 就不那么好用了。

Ian 写道,大的 batch 可以减少任务完成的总体时间,但又可能影响到 UI 的响应。

虽然这远远快于 8.5 秒的情况,但我们也作出了牺牲:那个总体速度较慢的例子反而为 UI 更快地提供了有用的信息。实际上,用户可能会更倾向于较慢的那个版本,因为有用的信息能够立即出现,你可能不会感觉到装载整个列表的速度慢了三倍——将整个列表拖动到底部的时间说不定都远远长于 8.5 秒了。按照这个重要的指标来看,单纯地使用异步方法是更好的选择:它为用户更快地提供了有用的信息。

Ian Griffiths 也谈到了使用线程池与 WPF 4.5 中的集合同步(Collection Synchronization)新特性。如果你要使用 ConfigureAwait(false) 方法来避免强制在 UI 线程上进行处理,这项技巧也是必不可少的。

调用 ConfigureAwait 表示我们并不关心方法在哪个线程上继续执行。要点在于,某个不能立即完成的读操作最终会完成,而方法的余下部分会在一个线程池的线程上延迟执行。这意味着使用 await 不会再导致 WPF 的调度占用。但当然,它也意味着列表的全部更新都会发生在一个工作线程上,因此我们需要使用同样的小花招以避免产生问题:要么等到方法完成后再绑定数据并显示列表,要么就必须处理跨线程的变更通知。

Ian 所演示的另一个技巧是使用 Reactive Extensions 分块处理数据。它使用了 Buffer 函数将 batch 大小限制为 100ms,或是将总数限制为 5000 个(取决于先达到哪个数值),然后通过 ObserveOnDispatcher 函数将其封装回送至 UI 线程。这种模式比起其它技巧显得冗余,但它“几乎能够立即开始显示 […] 数据,并在 2.3 秒内完成加载并显示所有数据”,这比原来的同步实现还是有所改进。

查看英文原文When to Use WPF with Async and Reactive Extensions


感谢杨赛对本文的审校。

给 InfoQ 中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ)或者腾讯微博(@InfoQ)关注我们,并与我们的编辑和其他读者朋友交流。

.NET语言 & 开发