C# 线程手册 第一章 线程定义 .NET 和 C# 对线程的支持
由于.NET Framework 支持自由線程,所以自由線程在所有.NET 語言中都存在,包括C#和VB.NET. 在下一部分,我們將著重關(guān)注如何提供這種支持以及更多關(guān)于線程是如何做到的,而不再關(guān)注線程是什么。我們將討論一些能夠進一步幫助區(qū)分進程的額外支持。
在這一部分的最后,你將理解:
1. 什么是System.AppDomain 類以及它可以幫助你做什么?
2. .NET runtime(運行時)如何監(jiān)控線程?
System.AppDomain
當(dāng)我們在這一章的早些時候解釋進程時,我們知道進程是對維系進程存在的內(nèi)存和資源的物理隔離。我們后來說到一個進程至少有一個線程。當(dāng)初微軟設(shè)計.NET Framework 時,它又添加了一層稱作應(yīng)用程序域或AppDomain的隔離。應(yīng)用程序域不是像進程那樣的物理隔離;它是進程內(nèi)部的進一步的邏輯隔離。由于在一個進程中可能有多個應(yīng)用程序域,所以我們有一些優(yōu)勢。大體上說,對標(biāo)準(zhǔn)進程來說不通過代理訪問其他進程的數(shù)據(jù)是不可能的, 而使用代理會導(dǎo)致重大開銷和代碼復(fù)雜化。然而,通過介紹應(yīng)用程序域的概念,我們現(xiàn)在可以在一個進程中運行多個程序。進程提供的隔離在應(yīng)用程序域中也存在。線程可以在不同應(yīng)用程序域間執(zhí)行而沒有與相關(guān)的內(nèi)部進程通信開銷。這些額外的進程內(nèi)部的壁壘的好處是他們對內(nèi)部數(shù)據(jù)提供類型檢查。
微軟將這些應(yīng)用程序域相關(guān)的所有功能封裝到一個System.AppDomain的類中。微軟.NET 程序集與這些應(yīng)用程序域之間有緊密聯(lián)系。任何時候當(dāng)一個程序集被加載到一個程序中時,它實際上是被加載到應(yīng)用程序域中。除非特別情況,程序集都會被加載到調(diào)用代碼的應(yīng)用程序域中。應(yīng)用程序域與線程也有一個直接關(guān)系;它們可以有一個或多個線程,就像進程一樣。然而,不同點是一個應(yīng)用程序域可能在進程內(nèi)部創(chuàng)建而不是通過新的線程創(chuàng)建。這個關(guān)系可以簡化成如圖9所示的模型。
圖9
?
在.NET 中,AppDomain和線程類由于安全原因而不能繼承。
每個應(yīng)用程序都包含一個或者多個AppDomains.每個AppDomain可以創(chuàng)建并執(zhí)行多個線程。下圖顯示在機器X上有兩個操作系統(tǒng)進程Y和Z。操作系統(tǒng)進程Y有四個應(yīng)用程序域:A,B,C和D。操作系統(tǒng)進程Z有兩個應(yīng)用程序域:A和B。
圖10
?
設(shè)置AppDomain數(shù)據(jù)
你已經(jīng)聽了理論看了模型;現(xiàn)在我們要動手寫點真正的代碼。在下面的例子中,我們將使用AppDomain設(shè)置數(shù)據(jù),收集數(shù)據(jù)并確定AppDomain中正在運行的線程。創(chuàng)建一個新的類文件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);
}
}
}
你的輸出應(yīng)該類似于:
這即使對于非C#開發(fā)人員來說也很直觀。然而,讓我們看一下代碼并確定究竟發(fā)生了什么。這是這個類的第一個重要地方:
public void SetDomainData(string vName, string vValue){
mDomain.SetData(vName, (object)vValue);
//mThreadId = AppDomain.GetCurrentThreadId();
mThreadId = Thread.CurrentThread.ManagedThreadId;
}
這個方法把設(shè)置名字和值的數(shù)據(jù)作為參數(shù)。你將會注意到SetData() 方法當(dāng)傳遞參數(shù)時已經(jīng)做了一些不同的事情。這里我們將字符串轉(zhuǎn)換成一個Object類型,因為SetData()的第二個參數(shù)類型是object. 由于我們僅使用一個字符串和一個繼承自System.Object的字符串,我們可以直接使用這個變量而不用把它強制轉(zhuǎn)換為一個對象。然而,其他的你想要存儲的數(shù)據(jù)可能不像現(xiàn)在這個容易處理。這個事實簡單地提醒我們已經(jīng)完成了這個轉(zhuǎn)換。在這個方法的最后部分,你將注意到我們可以通過對AppDomain對象的GetCurrentThreadId屬性的簡單調(diào)用獲取當(dāng)前執(zhí)行線程的ID(已經(jīng)過時,新方法是 Thread.CurrentThread.ManagedThreadId)。
讓我們繼續(xù)下一個方法:
public string GetDomainData(string name){
return (string)mDomain.GetData(name);
}
這個方法也很基礎(chǔ)。我們使用AppDomain類的GetData()方法獲取一個基于鍵值的數(shù)據(jù)。在這種情況下,我們僅是將GetDomainData()方法的參數(shù)傳遞給GetData()方法。我們將GetData方法的結(jié)果返回。
最后,讓我們看一下主方法:
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中存儲的名值對開始并向控制臺寫一段代碼提示我們方法已經(jīng)開始執(zhí)行。下一步,我們使用對當(dāng)前執(zhí)行的AppDomain對象(Main()方法中執(zhí)行的那個對象)的引用對我們類中的Domain字段賦值。下一步我們調(diào)用方法-傳遞參數(shù)給SetDomainData()方法:
obj.SetDomainData(dataName, dataValue);繼續(xù),我們向GetDomainData()方法傳遞一個參數(shù)來獲取我們剛設(shè)置的數(shù)據(jù)并把它插入到控制臺輸出流中。我們也輸出我們的類的ThreadId屬性來看當(dāng)前調(diào)用方法的ThreadId.
在一個特定AppDomain中執(zhí)行代碼
現(xiàn)在讓我們看一下如何創(chuàng)建一個新的應(yīng)用程序域并觀察當(dāng)在新創(chuàng)建的AppDomain中創(chuàng)建線程時的重要行為。下面代碼包含在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);
}
}
}
編譯類的輸出看起來應(yīng)該像這樣:
你會注意到我們在這個例子中創(chuàng)建了兩個應(yīng)用程序域。我們調(diào)用了AppDomain類中的靜態(tài)CreateDomain()方法。構(gòu)造函數(shù)參數(shù)是我們創(chuàng)建的AppDomain的一個友好名字。稍后我們將看到可以通過一個只讀屬性訪問這個友好名字。下面是創(chuàng)建AppDomain實例的代碼:
AppDomain domainA = AppDomain.CreateDomain("MyDomainA");下一步我們調(diào)用先前例子中的SetData()方法。由于之前已經(jīng)介紹過這個方法所以這里就略過。然而,我們需要解釋的是如何在一個給定的AppDomain中獲得代碼運行。通過AppDomain類中的DoCallBack()方法可以實現(xiàn)。這個方法將一個CrossAppDomainDelegate作為它的參數(shù)。在這種情況下,我們已經(jīng)創(chuàng)建了一個CrossAppDomainDelegate的實例并將它的名字作為參數(shù)傳遞給我們希望執(zhí)行的構(gòu)造函數(shù)中去。
CommonCallBack();CrossAppDomainDelegate delegateA = new CrossAppDomainDelegate(CommonCallBack);
domainA.DoCallBack(delegateA);
我們首先調(diào)用CommonCallBack()。這是要在主AppDomain的上下文中執(zhí)行CommonCallBack() 方法。你將看到輸出的是主AppDomain的FriendlyName屬性是執(zhí)行者名字。
最后,看一下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);
}
你會發(fā)現(xiàn)它非常原子化以至于不論在什么實例下運行都會工作。我們再次使用CurrentDomain屬性獲取執(zhí)行代碼的應(yīng)用程序域的一個引用。然后我們再次使用FriendlyName屬性確定我們在使用哪個AppDomain.
我們也調(diào)用了GetCurrentThreadId()方法(已過時,同上)。當(dāng)你查看輸出,你將看到不論我們在哪個AppDomain中執(zhí)行都會得到同樣的線程ID。需要知道不論一個AppDomain有沒有線程,線程都可以跨應(yīng)用程序域執(zhí)行,這是很重要的。
線程管理和.NET運行時
.NET Framework 提供比進程自由線程和邏輯應(yīng)用程序域還有多的特性。事實上,.NET Framework 提供對處理器線程的對象表示。這些對象表示是System.Threading.Thread類的實例。我們將在下一章深入探討。然而,再繼續(xù)下一章之前,我們必須了解非托管線程和托管線程是如何關(guān)聯(lián)的。那就是說,非托管線程(在.NET 世界之外創(chuàng)建的線程)實例有關(guān),后者表示運行在.NET CLR 中的線程。
.NET 運行時監(jiān)控所有由.NET代碼創(chuàng)建的線程。它也監(jiān)控所有可能在托管代碼中執(zhí)行的非托管線程。由于托管代碼可以通過COM-可調(diào)用包裝暴露,所以非托管線程運行在.NET運行時中是可能的。
當(dāng)非托管代碼運行在一個托管線程中,運行時將會檢查一個托管線程對象的TLS是否存在。如果找到了一個托管線程,運行時就會使用它。否則它將創(chuàng)建一個新的然后使用。這很簡單,但是需要注意。我們?nèi)韵胍玫揭粋€關(guān)于我們線程的對象表示而不管它來自哪里。如果運行時無法管理且為外部調(diào)用類型創(chuàng)建線程,我們將無法在托管環(huán)境中確定線程,甚至控制它。
關(guān)于線程管理最后一件重要的事是一旦非托管調(diào)用返回到非托管代碼中,運行時將無法繼續(xù)檢測它。
總結(jié)
我們在這一章講了很多內(nèi)容。關(guān)于什么是多任務(wù)以及如何通過使用線程實現(xiàn)多任務(wù)。知道了多任務(wù)和自由線程不是一回事兒。還講了進程以及如何與其他應(yīng)用程序隔離。我們也講述了Windows操作系統(tǒng)中線程方法。你現(xiàn)在知道Windows會將當(dāng)前線程中斷以使其他線程獲取一個簡單的周期作為運行時間。這個簡單的周期稱作一個時間片或間歇。我們也描述了線程優(yōu)先級功能和這些優(yōu)先級的不同級別,以及線程默認情況下會繼承父進程的優(yōu)先級。
我們也描述了.NET 運行時如何監(jiān)控在.NET環(huán)境中創(chuàng)建的線程以及在托管代碼中執(zhí)行的非托管線程。還描述了.NET Framework對線程的支持。System.AppDomain類在進程物理數(shù)據(jù)隔離的基礎(chǔ)上提供額外層的邏輯數(shù)據(jù)隔離。我們描述了線程如何輕松地從一個AppDomain到另外一個AppDomain. 還有我們也查看了為何一個AppDomain沒有像進程一樣有自己的線程。
總結(jié)
以上是生活随笔為你收集整理的C# 线程手册 第一章 线程定义 .NET 和 C# 对线程的支持的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 梦到猫产仔是什么意思
- 下一篇: 把一个结构体当做属性后碰到的问题