C# 線程手冊 第一章 線程定義 .NET 和 C# 對線程的支持


由於.NET Framework 支持自由線程,所以自由線程在所有.NET 語言中都存在,包括C#和VB.NET. 在下一部分,我們將着重關注如何提供這種支持以及更多關於線程是如何做到的,而不再關注線程是什么。我們將討論一些能夠進一步幫助區分進程的額外支持。

在這一部分的最后,你將理解:

1. 什么是System.AppDomain 類以及它可以幫助你做什么?

2. .NET runtime(運行時)如何監控線程?

System.AppDomain

當我們在這一章的早些時候解釋進程時,我們知道進程是對維系進程存在的內存和資源的物理隔離。我們后來說到一個進程至少有一個線程。當初微軟設計.NET Framework 時,它又添加了一層稱作應用程序域或AppDomain的隔離。應用程序域不是像進程那樣的物理隔離;它是進程內部的進一步的邏輯隔離。由於在一個進程中可能有多個應用程序域,所以我們有一些優勢。大體上說,對標准進程來說不通過代理訪問其他進程的數據是不可能的, 而使用代理會導致重大開銷和代碼復雜化。然而,通過介紹應用程序域的概念,我們現在可以在一個進程中運行多個程序。進程提供的隔離在應用程序域中也存在。線程可以在不同應用程序域間執行而沒有與相關的內部進程通信開銷。這些額外的進程內部的壁壘的好處是他們對內部數據提供類型檢查。

微軟將這些應用程序域相關的所有功能封裝到一個System.AppDomain的類中。微軟.NET 程序集與這些應用程序域之間有緊密聯系。任何時候當一個程序集被加載到一個程序中時,它實際上是被加載到應用程序域中。除非特別情況,程序集都會被加載到調用代碼的應用程序域中。應用程序域與線程也有一個直接關系;它們可以有一個或多個線程,就像進程一樣。然而,不同點是一個應用程序域可能在進程內部創建而不是通過新的線程創建。這個關系可以簡化成如圖9所示的模型。

圖9

 

在.NET 中,AppDomain和線程類由於安全原因而不能繼承。

每個應用程序都包含一個或者多個AppDomains.每個AppDomain可以創建並執行多個線程。下圖顯示在機器X上有兩個操作系統進程Y和Z。操作系統進程Y有四個應用程序域:A,B,C和D。操作系統進程Z有兩個應用程序域:A和B。

圖10

 

設置AppDomain數據

你已經聽了理論看了模型;現在我們要動手寫點真正的代碼。在下面的例子中,我們將使用AppDomain設置數據,收集數據並確定AppDomain中正在運行的線程。創建一個新的類文件appdomain.cs並輸入以下代碼:

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace MyAppDomain
{
class MyAppDomain
{
private AppDomain mDomain;
private int mThreadId;

public void SetDomainData(string vName, string vValue)
{
mDomain.SetData(vName, (object)vValue);
//mThreadId = AppDomain.GetCurrentThreadId();
mThreadId = Thread.CurrentThread.ManagedThreadId;
}

public string GetDomainData(string name)
{
return (string)mDomain.GetData(name);
}

static void Main(string[] args)
{
string dataName = "MyData";
string dataValue = "Some Data to be stored";

Console.WriteLine("Retrieving current domain");
MyAppDomain obj = new MyAppDomain();
obj.mDomain = AppDomain.CurrentDomain;

Console.WriteLine("Setting domain data");
obj.SetDomainData(dataName, dataValue);

Console.WriteLine("Getting domain data");
Console.WriteLine("The data found for key " + dataName
+ " is " + obj.GetDomainData(dataName)
+ " running on thread id: " + obj.mThreadId);
}
}
}

你的輸出應該類似於:

這即使對於非C#開發人員來說也很直觀。然而,讓我們看一下代碼並確定究竟發生了什么。這是這個類的第一個重要地方:

public void SetDomainData(string vName, string vValue)
{
mDomain.SetData(vName, (object)vValue);
//mThreadId = AppDomain.GetCurrentThreadId();
mThreadId = Thread.CurrentThread.ManagedThreadId;
}

這個方法把設置名字和值的數據作為參數。你將會注意到SetData() 方法當傳遞參數時已經做了一些不同的事情。這里我們將字符串轉換成一個Object類型,因為SetData()的第二個參數類型是object. 由於我們僅使用一個字符串和一個繼承自System.Object的字符串,我們可以直接使用這個變量而不用把它強制轉換為一個對象。然而,其他的你想要存儲的數據可能不像現在這個容易處理。這個事實簡單地提醒我們已經完成了這個轉換。在這個方法的最后部分,你將注意到我們可以通過對AppDomain對象的GetCurrentThreadId屬性的簡單調用獲取當前執行線程的ID(已經過時,新方法是 Thread.CurrentThread.ManagedThreadId)。

讓我們繼續下一個方法:

public string GetDomainData(string name)
{
return (string)mDomain.GetData(name);
}

這個方法也很基礎。我們使用AppDomain類的GetData()方法獲取一個基於鍵值的數據。在這種情況下,我們僅是將GetDomainData()方法的參數傳遞給GetData()方法。我們將GetData方法的結果返回。

最后,讓我們看一下主方法:

static void Main(string[] args)
{
string dataName = "MyData";
string dataValue = "Some Data to be stored";

Console.WriteLine("Retrieving current domain");
MyAppDomain obj = new MyAppDomain();
obj.mDomain = AppDomain.CurrentDomain;

Console.WriteLine("Setting domain data");
obj.SetDomainData(dataName, dataValue);

Console.WriteLine("Getting domain data");
Console.WriteLine("The data found for key "
+ dataName
+ " is " + obj.GetDomainData(dataName)
+ " running on thread id: " + obj.mThreadId);
}

我們通過初始化我們想在AppDomain中存儲的名值對開始並向控制台寫一段代碼提示我們方法已經開始執行。下一步,我們使用對當前執行的AppDomain對象(Main()方法中執行的那個對象)的引用對我們類中的Domain字段賦值。下一步我們調用方法-傳遞參數給SetDomainData()方法:

obj.SetDomainData(dataName, dataValue);

繼續,我們向GetDomainData()方法傳遞一個參數來獲取我們剛設置的數據並把它插入到控制台輸出流中。我們也輸出我們的類的ThreadId屬性來看當前調用方法的ThreadId.

在一個特定AppDomain中執行代碼

現在讓我們看一下如何創建一個新的應用程序域並觀察當在新創建的AppDomain中創建線程時的重要行為。下面代碼包含在create_appdomain.cs中:

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace MyAppDomain
{
public class CreateAppDomains
{
static void Main(string[] args)
{
AppDomain domainA = AppDomain.CreateDomain("MyDomainA");
string stringA = "DomainA Value";
domainA.SetData("DomainKey", stringA);

CommonCallBack();

CrossAppDomainDelegate delegateA =
new CrossAppDomainDelegate(CommonCallBack);
domainA.DoCallBack(delegateA);
}

static void CommonCallBack()
{
AppDomain domain = AppDomain.CurrentDomain;
Console.WriteLine("The value '" + domain.GetData("DomainKey")
+ "' was found in " + domain.FriendlyName
+ " running on thread id: "
+ Thread.CurrentThread.ManagedThreadId);
}
}
}

編譯類的輸出看起來應該像這樣:

你會注意到我們在這個例子中創建了兩個應用程序域。我們調用了AppDomain類中的靜態CreateDomain()方法。構造函數參數是我們創建的AppDomain的一個友好名字。稍后我們將看到可以通過一個只讀屬性訪問這個友好名字。下面是創建AppDomain實例的代碼:

AppDomain domainA = AppDomain.CreateDomain("MyDomainA");

下一步我們調用先前例子中的SetData()方法。由於之前已經介紹過這個方法所以這里就略過。然而,我們需要解釋的是如何在一個給定的AppDomain中獲得代碼運行。通過AppDomain類中的DoCallBack()方法可以實現。這個方法將一個CrossAppDomainDelegate作為它的參數。在這種情況下,我們已經創建了一個CrossAppDomainDelegate的實例並將它的名字作為參數傳遞給我們希望執行的構造函數中去。

CommonCallBack();

CrossAppDomainDelegate delegateA = new CrossAppDomainDelegate(CommonCallBack);
domainA.DoCallBack(delegateA);

我們首先調用CommonCallBack()。這是要在主AppDomain的上下文中執行CommonCallBack() 方法。你將看到輸出的是主AppDomain的FriendlyName屬性是執行者名字。

最后,看一下CommonCallBack()方法本身:

static void CommonCallBack()
{
AppDomain domain = AppDomain.CurrentDomain;
Console.WriteLine("The value '" + domain.GetData("DomainKey")
+ "' was found in " + domain.FriendlyName
+ " running on thread id: "
+ Thread.CurrentThread.ManagedThreadId);
}

你會發現它非常原子化以至於不論在什么實例下運行都會工作。我們再次使用CurrentDomain屬性獲取執行代碼的應用程序域的一個引用。然后我們再次使用FriendlyName屬性確定我們在使用哪個AppDomain.

我們也調用了GetCurrentThreadId()方法(已過時,同上)。當你查看輸出,你將看到不論我們在哪個AppDomain中執行都會得到同樣的線程ID。需要知道不論一個AppDomain有沒有線程,線程都可以跨應用程序域執行,這是很重要的。

線程管理和.NET運行時

.NET Framework 提供比進程自由線程和邏輯應用程序域還有多的特性。事實上,.NET Framework 提供對處理器線程的對象表示。這些對象表示是System.Threading.Thread類的實例。我們將在下一章深入探討。然而,再繼續下一章之前,我們必須了解非托管線程托管線程是如何關聯的。那就是說,非托管線程(在.NET 世界之外創建的線程)實例有關,后者表示運行在.NET CLR 中的線程。

.NET 運行時監控所有由.NET代碼創建的線程。它也監控所有可能在托管代碼中執行的非托管線程。由於托管代碼可以通過COM-可調用包裝暴露,所以非托管線程運行在.NET運行時中是可能的。

當非托管代碼運行在一個托管線程中,運行時將會檢查一個托管線程對象的TLS是否存在。如果找到了一個托管線程,運行時就會使用它。否則它將創建一個新的然后使用。這很簡單,但是需要注意。我們仍想要得到一個關於我們線程的對象表示而不管它來自哪里。如果運行時無法管理且為外部調用類型創建線程,我們將無法在托管環境中確定線程,甚至控制它。

關於線程管理最后一件重要的事是一旦非托管調用返回到非托管代碼中,運行時將無法繼續檢測它。

總結

我們在這一章講了很多內容。關於什么是多任務以及如何通過使用線程實現多任務。知道了多任務和自由線程不是一回事兒。還講了進程以及如何與其他應用程序隔離。我們也講述了Windows操作系統中線程方法。你現在知道Windows會將當前線程中斷以使其他線程獲取一個簡單的周期作為運行時間。這個簡單的周期稱作一個時間片或間歇。我們也描述了線程優先級功能和這些優先級的不同級別,以及線程默認情況下會繼承父進程的優先級。

我們也描述了.NET 運行時如何監控在.NET環境中創建的線程以及在托管代碼中執行的非托管線程。還描述了.NET Framework對線程的支持。System.AppDomain類在進程物理數據隔離的基礎上提供額外層的邏輯數據隔離。我們描述了線程如何輕松地從一個AppDomain到另外一個AppDomain. 還有我們也查看了為何一個AppDomain沒有像進程一樣有自己的線程。


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com