体系结构
我们可以建立一个体系结构来保护UI和工作代码,防止它们进行线程间交互。事实上,我们可以实现一个构架来执行那些复杂代码,我们能使用该构架来管理或控制后台线程与UI的交互。
首先让我们讨论该体系结构,然后再设计和实现该代码。
典型的情况下,一个应用程序以一个线程开始,该线程打开用户界面。为了直观我们称该线程为UI线程。在很多应用程序中这是唯一的线程,因此它控制UI并做所有的处理。
在本例中,我们将建立一个工作线程从事后台处理,让UI线程聚焦于用户界面,这样工作线程在忙于工作时,UI线程仍然可以响应用户。
在UI线程与工作线程之间我们插入一层代码作为UI和工作代码间的接口。该代码本质上是控制器(Controller),管理和控制工作线程与UI线程间的交互。
图1:UI线程,控制器和工作线程
控制器将包含所有这些代码:安全地开始工作线程,将工作线程的任何状态信息传递给UI线程,将任何“取消”请求从UI线程传递给工作线程。UI代码与工作代码并不直接交互;它们通过控制器代码交互。
例外情况是工作线程被激活之前或完成之后,UI代码可以与Worker对象交互。在工作线程启动前,UI代码能建立和初始化Worker对象;在工作线程被终止后,UI可以从Worker对象检索到任意值。下面是UI角度的的事件流程:
1、建立Worker对象。
2、初始化Worker对象。
3、调用控制器启动工作线程。
a.Worker对象能通过控制器向UI发送状态信息。
b.UI能通过控制器向Worker对象发送“取消”请求。
4、当Worker对象结束后它通过控制器通知UI。
5、值可以直接从Worker对象检索到。
除了工作线程活动时,UI代码不能直接与Worker对象交互外,对UI来说没有特殊的代码需求。当后台处理在运行时UI保持活动并响应用户。
Worker对象角度的事件流程如下:
1、UI代码创建Worker对象。
2、UI代码使用需要的值来初始化Worker对象。
3、控制器创建后台线程并调用Worker对象的方法。
a. Worker对象运行工作代码。
b. 对象将任何状态信息传递给控制器,这样控制器才能将信息传递给UI。
c. 适当时,Worker对象查看是否有“取消”请求,如果有,就停止。
d. 当Worker对象完成后,它告诉控制器工作已经结束,这样控制器可以将该信息传递给UI。
4、现在工作线程终止,UI能直接与Worker对象交互。
由于工作代码只与控制器交互,我们不用担心工作线程突然与UI组件交互,而这种偶然的交互可能影响应用程序。另外,工作代码依赖于控制器才能与UI线程正确地通信,因此它们都是安全的。
这意味着我们在工作代码中不需要处理任何线程的问题,而只需要处理Worker对象的实例变量。
考虑不同组件,特别是在不同线程上的不同组件之间的交互通常最好使用图。Microsoft? Visio?支持建立UML(通用建模语言)图,它对我们非常有帮助。
下面是阐明UI、Worker对象和Controller之间事件流程的UML序列图。该图假定没有“取消”请求。所有的代码在UI线程上运行。
图2:过程流序列图
了解相同信息的另一种方法是使用UML行为图。这一类图更注重事务而不是对象,因此它显示了事务发生的步骤和过程是怎样一步一步地进行的。我们可以轻易地看出UI代码在左边的线程中工作,而Worker对象在右边的另一个线程中工作。Worker对象在另一个线程中运行之前和之后,它可以被UI直接地使用,进行初始化,然后检索结果。
图3:显示过程流的行为图
使用这类图能帮我们查明后台线程活动时UI与工作线程偶然交互的地方。任何偶然的交互都需要额外的代码来避免bug影响应用程序的稳定性。理想的情况是,这样的交互通过Controller组件发送,在那儿(Controller中)包含使这种交互安全的所有代码。
下图显示了如果UI作出“取消”请求的事件次序。
图4:有“取消”请求的次序图
注意:“取消”请求从UI发送到Controller,直到Worker与Controller检查是否有“取消”请求出现。UI和Controller都不强迫工作代码终止,它们让工作代码在自己地周期内温和并且安全地终止。