Threading in the Windows Runtime: Part 1

在Build 2013上Marytn Lovell解释了WinRT多线程的关键点Windows Runtime Internals: Understanding the Threading Model

  • 对长期运行的任务采用异步操作
  • 除了UI对象以外,保证其他对象是灵活对象(agile objects)
  • 不要阻塞UI线程

对应用来说,主要分UI线程和非UI线程。UI对象只能存在UI线程上,而大部分其他对象可以在任意线程上使用。这和传统的COM不太一致,传统的COM提供了一大堆线程类型和多线程模型。

WinRT和COM差别的地方在于,WinRT的非UI对象可以在任意线程上创建和销毁;而COM对象则会绑定到其创建线程,如果要移到其他线程上,需要特殊的函数调用。

之所以把UI对象锁定在UI线程上,是因为UI操作基本上是序列化的,不适合多线程操作。虽然UI对象不能在其他UI线 程上使用,但是一个应用可以有多个UI线程。其中的UI主线程在应用的生命周期内都存在。

COM是消息驱动的,当一个COM线程在执行一个消息请求的过程中执行外调并等待结果的时候,意味着它可以执行其他消息请求。这种可重入性对UI操作的序列化是不利的。为此,WinRT 8.1引入了ASTA线程模型,只提供有限的可重入性,只有逻辑上关联的线程(logically connected threads)可以在调用UI线程,而且这种调用是序列化的。一个调用执行的时候,其他调用必须等待。

上面的方式,引入了死锁的可能性。如果所有调用线程都在等待一个资源,而持有这个资源的线程在等待其他资源,就死锁了。由于死锁跟执行顺序有关,比较难以复现。于是乎,微软的做法是,当发现有死锁的可能性的时候,让调用立即失败,从而避免死锁发生。这种情况下,UI线程之间的交互需要通过Dispatcher。或者把对象创建在线程池上。

通常桌面程序如果不能相应操作系统的响应轮询,在任务管理器中这个程序就会被标识被无响应。对于WinRT程序而言则更加严格,长时间无响应的程序会被中止。如果需要执行耗时的任务,应该使用后台操作。

在WinJS/HTML中,所有的线程都是UI线程,即便是Web Worker线程。如果要使用线程池,则要借助C++等语言。

对于XAML来说,所有的XAML对象都是线程相关的,而且都属于同一个线程上的同一颗可视化树。不能把XAML控件挪到另外一个UI线程上。这和Win32有所区别,Win32模型下,一个hwnd(窗口句柄)可以包含另一个线程上的hwnd的。

Threading in the Windows Runtime: Part 2

Marshalling(列集)是COM的一个术语,指的是允许对象被另外一个线程或进程使用。大部分WinRT对象不需要列集,个别需要的也由操作系统代办。

COM和WinRT在列集上有一个区别,在WinRT中,代理对象是灵活的,可以由一个线程挪向另一个线程。列集出现在代理对象和被其代理对象之间的调用上。

接口IMarshal和INoMashal用来指示对象是否需要参与列集。WinRT对象不需要列集,因为他们大都是UI对象或者灵活对象。

注意:线程安全性和灵活性并不是相关的概念。一个灵活对象可能在其他线程调用,但是并不一定意味着这个是安全的操作。

WinRT创建的线程如果不是UI线程的话,默认都是WinRT线程。这样可以避免CoInitializeEx中的那些多线程模型选项。

UI线程上执行的异步操作,结果必须返回到UI线程。这需要在语言层面提供一定的支持。

前面提到,灵活对象可以在不同的线程上挪动,灵活对象不会随着线程销毁而销毁。而且灵活对象可以引用其他灵活对象。如果你不知道引用的其他对象是否是灵活对象,可以创建灵活引用来包裹其他对象。

在桌面COM上,为了保持对象线程不死,需要朝其不断发送消息。

如果知道一个对象是否是灵活对象,检查它是否实现IAgileObject接口。如果没有实现此接口,那么可以使用RoGetAgileReference来获取一个IAgileReference 。这是WinRT8.1引进的,以前的话,需要global interface table获取。

WinRT尽量不让开发者察觉COM套间的使用和存在,虽然WinRT使用了以下套间模型:

  • ASTA – Application Single Threaded Apartment, UI Threads
  • MTA – Multi-threaded Apartment, Thread Pool Threads
  • NTA – Neutral-threaded Apartment, Helpers for inter-process calls

有了灵活对象的存在,对象的生命周期可以不必随着套间或者线程的消失而终结。一种情况除外,那就是代理所指向的进程(比如System Broker)被终止。

其他参考

(本篇完)