代码改变世界

C# 线程手册 第四章 线程设计原则

2012-02-20 20:23  DanielWise  阅读(3459)  评论(2编辑  收藏  举报

概述

  大多数可扩展系统是具有高度并发性的,这意味着可能存在多个针对同一个对象的并发请求。实现一个既具有高并发性又具有线程安全性的代码是一个很大的挑战,因为这意味着当多个线程访问共享数据时,不会发生数据崩溃或者不一致的情况。

  如果我们借助一个正式的线程模型使用多线程技术,我们可以写出运行在一个并发情景中的具有高度扩展性的代码。在之前章节,我们学习了何时应该使用多线程以及关于多线程的所有细节,包括线程陷阱。在本章,我们将学习.NET 支持的所有线程模型以及如何充分利用这些线程模型的优势,同时要学习在.NET 上改进一些模型来帮助我们设计代码。

  所有的.NET 应用程序默认都是多线程的(VB 6.0 除外)。在Windows 窗体程序中,有一个特殊的线程(UI 线程)它控制所有用户接口相关功能,比如键盘活动和鼠标活动。当在UI线程上运行了一个耗时的操作时,应用程序将会没有响应。如果你在一个新创建的线程上而非UI线程上运行这类任务时,那么应用程序界面就不会卡死。然而,如果你认为仅能通过创建新线程解决这类问题的话,那么你错了。除了使用多线程技术,我们还可以使用异步编程和基于定时器功能的模型,这些已经在第二章描述过了。

  应用程序中的多线程

  如果你之前使用VB编程的话,你可能会知道VB 使用一个COM容器来支持多线程,比如MTS 或者 COM+。尽管VB5/6支持多线程,实际上它们仅支持单线程套件(Single Threaded Apartments, STA)。如果你用过Visual C++,那么在编译时你可以选择MTA应用程序(多线程套件)和STA 应用程序。然而.NET Framework 并没有套件的概念,它只在应用程序域中管理线程。默认情况下,所有的.NET 应用程序都是多线程的并且任何代码在任何时间都可以访问同一个对象。因此,我们必须对托管代码中的静态资源非常小心。

  .NET Framework 支持托管线程和非托管线程以及Win32中所有线程模型,比如STA和MTA。当你试图从托管代码中访问COM组件时,非托管线程就会被传统COM组件创建。.NET Framework 中不论托管代码还是非托管代码中的线程都由Thread 对象创建。

  如果你使用Win32 APIs实现过多线程程序,你可能记得Win32 支持用户接口线程工作线程。我们在第一章曾说过,线程名称目前已经分别改为套件模型线程自由线程。.NET Framework 支持两个基本的线程模型,它们分别是套件模型线程或单线程套件(Single Threaded Apartment, STA), 自由线程或多线程套件(Multi Threaded Apartment, MTA)组件。当我们在.NET 中创建一个线程时,默认是一个MTA线程。

  当你想访问基于STA的COM组件时(比如VB6 COM 组件),你应该仅使用STA线程模型。否则,你不应该把当前线程标志为STA,因为它涉及应用程序中的一个重要性能问题。

  回顾一下之前学到的,一个套件就是应用程序域中的用来在同一个上下文中分享线程的一个逻辑容器。在激活进程并创建一个对象过程中,应用程序域和上下文中的对象就被创建了。

STA 线程模型

一个STA线程单元建立在一个称作对象-客户端的模型基础上,这意味着创建STA线程单元的代码拥有线程。在任何一个单元中都只有一个线程,如图1所示

STA

图 1

  在STA线程模型中,所有对一个线程的调用都会被放到一个队列中然后调用按顺序一个接一个的被处理。因此,STA线程永远都不会有多个方法同步执行。STA线程有自己的私有数据而且它们彼此不分享数据。这能够保证线程模型的安全性且避免了数据崩溃和同步问题。然而,这却着实限制了程序员可用的选项,还导致了性能损耗,因为每当创建一个新线程所有数据都要拷贝给它。

  正如你从图 1 所看到的那样,应用程序域 X 有两个STA线程, X 和 Y, 每个STA 单元仅有一个线程。当定义线程以及创建线程的代码间的关系时使用了线程关联的概念。当调用一个STA单元线程时,调用者和线程之间的调用会由应用程序域的上下文处理,上下文维护着线程关联性。

  如果你的托管应用程序将要使用非托管传统COM组件,那么在访问它们之前了解COM组件的线程模型是非常重要的。如果在你的应用中没有标记正确的线程模型,那么可能会有一些未知的bugs 和灾难性的错误。线程模型信息可以在注册表中的HKEY_CLASSES_ROOT/CLSID/{COM 组件的类标识符}\InProcServer32 键值里找到。

  如果你想确认你正在使用的编程模型单元,那么在Main() 方法上应用STAThreadAttribute 属性:

[STAThreadAttribute]
static void Main()
{
    //...
}

  这个属性应该仅当你从托管代码中访问传统STA组件时使用。否则使用MTAThreadAttribute属性标志Main() 方法:

[MTAThreadAttribute]
static void Main()
{
    //...
}

  以上原则对ASP.NET 应用程同样适用。如果你的ASP.NET 页正在访问一个STA COM组件,那么你应该在ASP.NET 页的上面直接使用ASPCompat:

<%@ Page AspCompat="true" %>

  默认情况下,当我们直接使用AspCompat的话,那么所有的ASP.NET 页都是多线程的,ASP.NET 被标识为STA。这保证ASP.NET 页与一个COM组件的线程模型兼容。

  当你标志ASP.NET 页以使其运行在STA线程模型下时,应用程序的性能可能会受影响。

注意: 如果你正在使用VB.NET,那么你可以使用CreateObject 语句来实例化COM对象。由于C# 不允许后期绑定,在后期绑定中调用COM对象的唯一方式是使用反射。

 

下一篇介绍MTA 线程模型…