C# 線程手冊 第四章 線程設計原則


概述

  大多數可擴展系統是具有高度並發性的,這意味着可能存在多個針對同一個對象的並發請求。實現一個既具有高並發性又具有線程安全性的代碼是一個很大的挑戰,因為這意味着當多個線程訪問共享數據時,不會發生數據崩潰或者不一致的情況。

  如果我們借助一個正式的線程模型使用多線程技術,我們可以寫出運行在一個並發情景中的具有高度擴展性的代碼。在之前章節,我們學習了何時應該使用多線程以及關於多線程的所有細節,包括線程陷阱。在本章,我們將學習.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 線程模型…


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM