額基本脫離了2.0 3.5的時代了。在.net 4.0+ 時代。一切都是辣麼簡單!html
參考文檔:web
http://www.cnblogs.com/linzheng/archive/2012/04/11/2442061.html算法
http://www.cnblogs.com/pugang/archive/2011/11/09/2242380.html數據庫
http://www.cnblogs.com/LoveJenny/archive/2012/03/13/2392747.htmlexpress
http://www.cnblogs.com/luqixinhe/archive/2013/07/18/3197645.html編程
http://www.cnblogs.com/yank/p/3239767.htmlc#
http://www.cnblogs.com/apsnet/archive/2012/07/08/2581475.htmlwindows
http://www.cnblogs.com/heyuquan/archive/2013/04/18/Task-based-Asynchronous-Pattern.html設計模式
msdn 解釋以下:數組
「協變」是指可以使用與原始指定的派生類型相比,派生程度更大的類型。
「逆變」則是指可以使用派生程度更小的類型。
解釋的很正確,大體就是這樣,不過不夠直白。
直白的理解:
「協變」->」和諧的變」->」很天然的變化」->string->object :協變。
「逆變」->」逆常的變」->」不正常的變化」->object->string 逆變。
上面是我的對協變和逆變的理解,比起記住那些派生,類型,原始指定,更大,更小之類的詞語,我的認爲要容易點。
下面是一則笑話:
一個星期的每一天應該這樣念:
星期一 = 忙day;
星期二 = 求死day;
星期三 = 未死day;
星期四 = 受死day;
星期五 = 福來day;
星期六 = 灑脫day;
星期天 = 傷day
爲了演示協變和逆變,以及之間的區別,請建立控制檯程序CAStudy,手動添加兩個類:
由於是演示,因此都是個空類,
只是有一點記住Dog 繼承自Animal,
因此Dog變成Animal 就是和諧的變化(協變),而若是Animal 變成Dog就是不正常的變化(逆變)
在Main函數中輸入:
由於Dog繼承自Animal,因此Animal aAnimal = aDog; aDog 會隱式的轉變爲Animal.
可是List<Dog> 不繼承List<Animal> 因此出現下面的提示:
若是想要轉換的話,應該使用下面的代碼:
List<Animal> lstAnimal2 = lstDogs.Select(d => (Animal)d).ToList();
能夠看到一個lstDogs 變成lstAnimal 是多麼複雜的操做了。
正因如此,因此微軟新增了兩個關鍵字:Out,In,下面是他們的msdn解釋:
協變的英文是:「covariant」,逆變的英文是:「Contravariant」
爲何Microsoft選擇的是」Out」 和」In」 做爲特性而不是它們呢?
我我的的理解:
由於協變和逆變的英文太複雜了,並無體現協變和逆變的不一樣,可是out 和 in 卻很直白。
out: 輸出(做爲結果),in:輸入(做爲參數)
因此若是有一個泛型參數標記爲out,則表明它是用來輸出的,只能做爲結果返回,而若是有一個泛型參數標記爲in,則表明它是用來輸入的,也就是它只能做爲參數。
目前out 和in 關鍵字只能在接口和委託中使用,微軟使用out 和 in 標記的接口和委託大體以下:
先看下第一個IEnumerable<T>
和剛開始說的同樣,T 用out 標記,因此T表明了輸出,也就是隻能做爲結果返回。
public static void Main()
{
Dog aDog = new Dog();
Animal aAnimal = aDog;
List<Dog> lstDogs = new List<Dog>();
//List<Animal> lstAnimal = lstDogs;
List<Animal> lstAnimal2 = lstDogs.Select(d => (Animal)d).ToList();
IEnumerable<Dog> someDogs = new List<Dog>();
IEnumerable<Animal> someAnimals = someDogs;
}
由於T只能作結果返回,因此T不會被修改, 編譯器就能夠推斷下面的語句強制轉換合法,因此
IEnumerable<Animal> someAnimals = someDogs;
能夠經過編譯器的檢查,反編譯代碼以下:
雖然經過了C#編譯器的檢查,可是il 並不知道協變和逆變,仍是得乖乖的強制轉換。
在這裏我看到了這句話:
IEnumerable<Animal> enumerable2 = (IEnumerable<Animal>) enumerable1;
那麼是否是能夠List<Animal> lstAnimal3 = (List<Animal>)lstDogs; 呢?
想要回答這個問題須要在回頭看看Clr via C# 關於泛型和接口的章節了,我就不解釋了,
答案是不能夠。
上面演示的是協變,接下來要演示下逆變。
爲了演示逆變,那麼就要找個in標記的接口或者委託了,最簡單的就是:
在Main函數中添加:
Action<Animal> actionAnimal = new Action<Animal>(a => {/*讓動物叫*/ });
Action<Dog> actionDog = actionAnimal;
actionDog(aDog);
很明顯actionAnimal 是讓動物叫,由於Dog是Animal,那麼既然Animal 都能叫,Dog確定也能叫。
In 關鍵字:逆變,表明輸入,表明着只能被使用,不能做爲返回值,因此C#編譯器能夠根據in關鍵字推斷這個泛型類型只能被使用,因此Action<Dog> actionDog = actionAnimal;能夠經過編譯器的檢查。
再次演示Out關鍵字:
添加兩個類:
public interface IMyList<out T>
{
T GetElement();
}
public class MyList<T> : IMyList<T>
{
public T GetElement()
{
return default(T);
}
}
由於out 關鍵字,因此下面的代碼能夠經過編譯
IMyList<Dog> myDogs = new MyList<Dog>();
IMyList<Animal> myAnimals = myDogs;
將上面的兩個類修改成:
public interface IMyList<out T>
{
T GetElement();
void ChangeT(T t);
}
public class MyList<T> : IMyList<T>
{
public T GetElement()
{
return default(T);
}
public void ChangeT(T t)
{
//Change T
}
}
編譯:
由於T被out修飾,因此T只能做爲參數。
一樣修改兩個類以下:
public interface IMyList<in T>
{
T GetElement();
void ChangeT(T t);
}
public class MyList<T> : IMyList<T>
{
public T GetElement()
{
return default(T);
}
public void ChangeT(T t)
{
//Change T
}
}
這一次使用in關鍵字。
編譯:
由於用in關鍵字標記,因此T只能被使用,不能做爲返回值。
最後修改代碼爲:
public interface IMyList<in T>
{
void ChangeT(T t);
}
public class MyList<T> : IMyList<T>
{
public void ChangeT(T t)
{
//Change T
}
}
編譯成功,由於in表明了逆變,因此
IMyList<Animal> myAnimals = new MyList<Animal>();
IMyList<Dog> myDogs = myAnimals;
能夠編譯成功!。
看到過園子裏面幾篇協變和逆變的文章,可是總以爲寫得不夠清晰,文章這東西注重要是要把本身想表達的觀點表達出來,這個過程應該是把複雜的東西消化出來從而簡單化,清晰化,而不是故弄玄虛,反其道而行之,下面咱們言歸正傳啦。
咱們先來看一段MSDN原文給協變,逆變和變體下個定義:
A generic interface or delegate is called variant if its generic parameters are declared covariant or contravariant. Both C# and Visual Basic enable you to create your own variant interfaces and delegates.
若是泛型接口或委託的泛型參數聲明爲協變或逆變,則將該泛型接口或委託稱爲「變體」。 C# 和 Visual Basic 都容許您建立本身的變體接口和委託。
通俗解釋:
變體定義:帶有協變或逆變參數的泛型接口或委託。也就是說協變和逆變主要關注點在泛型接口或委託。
那什麼又是協變和逆變呢?
咱們先來看下面一個來自MSDN的例子:
1 // 協變
2 IEnumerable<string>strings = new List<string>();
3 IEnumerable<object> objects = strings;
你們看到了麼一個聲明爲IEnumerable<string>的 接口類型被賦給了一個更低 級別的IEnumerable<object>.
對,這就是協變。再來看一個例子:
class Base
{
public static void PrintBases(IEnumerable<Base> bases)
{
foreach(Base b in bases)
{
Console.WriteLine(b);
}
}
}
class Derived : Base
{
public static void Main()
{
List<Derived> dlist = new List<Derived>();
Derived.PrintBases(dlist);//因爲IEnumerable<T>接口是協變的,因此PrintBases(IEnumerable<Base> bases)
//能夠接收一個更加具體化的IEnumerable<Derived>做爲其參數。
IEnumerable<Base> bIEnum = dlist;
}
}
下面給協變下個定義:
協變:讓一個帶有協變參數的泛型接口(或委託)能夠接收類型更加精細化,具體化的泛型接口(或委託)做爲參數,能夠當作OO中多態的一個延伸。
// 逆變
// Assume that the following method is in the class:
// static void SetObject(object o) { }
Action<object> actObject = SetObject;
Action<string> actString = actObject;
//委託actString中之後要使用更加精細化的類型string不能再使用object啦!
string strHello(「Hello」);
actString(strHello);
你們看到了麼?一個聲明爲Action<object>的類型被賦給了一個Action<string>,你們都知道,Action<T>接收參數,沒有返回值,因此其中的object和string是其參數,這個過程其實就是參數的約束更增強了,也就是說讓參數類型更加精細化。下面咱們來給逆變下個定義:
逆變:讓一個帶有協變參數的泛型接口(或委託)能夠接收粒度更粗的泛型接口或委託做爲參數,這個過程其實是參數類型更加精細化的過程。
一句話總結:協變讓一個粗粒度接口(或委託)能夠接收一個更加具體的接口(或委託)做爲參數(或返回值);逆變讓一個接口(或委託)的參數類型(或返回值)類型更加具體化,也就是參數類型更強,更明確。
一般,協變類型參數可用做委託的返回類型,而逆變類型參數可用做參數類型。 對於接口,協變類型參數可用做接口的方法的返回類型,而逆變類型參數可用做接口的方法的參數類型。
傳送門:異步編程系列目錄……
最近我爲你們陸續介紹了「IAsyncResult異步編程模型 (APM)」和「基於事件的異步編程模式(EAP)」兩種異步編程模型。在.NET4.0 中Microsoft又爲咱們引入了新的異步編程模型「基於任務的異步編程模型(TAP)」,而且推薦咱們在開發新的多線程應用程序中首選TAP,在.NET4.5中更是對TPL庫進行了大量的優化與改進。那如今我先介紹下TAP具備哪些優點:
- 目前版本(.NET4.X)的任務調度器(TaskScheduler)依賴於底層的線程池引擎。經過局部隊列的任務內聯化(task inlining)和工做竊取機制能夠爲咱們提高程序性能。
- 輕鬆實現任務等待、任務取消、延續任務、異常處理(System.AggregateException)、GUI線程操做。
- 在任務啓動後,能夠隨時以任務延續的形式註冊回調。
- 充分利用現有的線程,避免建立沒必要要的額外線程。
- 結合C#5.0引入async和await關鍵字輕鬆實現「異步方法」。
示例源碼:異步編程:.NET 4.5 基於任務的異步編程模型(TAP).rar
術語:
APM 異步編程模型,Asynchronous Programming Model
EAP 基於事件的異步編程模式,Event-based Asynchronous Pattern
TAP 基於任務的異步編程模式,Task-based Asynchronous Pattern
TPL 任務並行庫,Task Parallel Library
理解CLR線程池引擎、理解全局隊列、理解線程的局部隊列及性能優點
- CLR線程池引擎
CLR線程池引擎維護了必定數量的空閒工做線程以支持工做項的執行,而且可以重用已有的線程以免建立新的沒必要要的線程所花費的昂貴的處理過程。而且使用登山算法(hill-climbing algorithm)檢測吞吐量,判斷是否可以經過更多的線程來完成更多的工做項。這個算法的判斷依據是工做項所需某些類型資源的可用狀況,例如:CPU、網絡帶寬或其餘。此外這個算法還會考慮一個飽和點,即達到飽和點的時候,建立更多地線程反而會下降吞吐量。(線程池的詳細介紹請看《異步編程:使用線程池管理線程》)
目前版本的TAP的任務調度器(TaskScheduler)基於CLR線程池引擎實現。當任務調度器(TaskScheduler)開始分派任務時:
1) 在主線程或其餘並無分配給某個特定任務的線程的上下文中建立並啓動的任務,這些任務將會在全局隊列中競爭工做線程。這些任務被稱爲頂層任務。
2) 然而,若是是在其餘任務的上下文中建立的任務(子任務或嵌套任務),這些任務將被分配在線程的局部隊列中。
嵌套任務:
是在另外一個任務的用戶委託中建立並啓動的任務。
子任務:
是使用TaskCreationOptions.AttachedToParent選項建立頂層任務的嵌套任務或延續任務;或使用TaskContinuationOptions.AttachedToParent選項建立的延續任務的嵌套任務或延續任務。(應用程序使用TaskCreationOptions.DenyChildAttach選項建立父任務。此選項指示運行時會取消子任務的AttachedToParent規範)
若是你不想特定的任務放入線程的局部隊列,那麼能夠指定TaskCreationOptions.PreferFairness或TaskContinuationOptions.PreferFairness枚舉參數。(使Task與ThreadPool.QueueUserWorkItem行爲相同)
- 線程池的全局隊列
當調用ThreadPool.QueueUserWorkItem()添加工做項時,該工做項會被添加到線程池的全局隊列中。線程池中的空閒線程以FIFO的順序將工做項從全局隊列中取出並執行,但並不能保證按某個指定的順序完成。
線程的全局隊列是共享資源,因此內部會實現一個鎖機制。當一個任務內部會建立不少子任務時,而且這些子任務完成得很是快,就會形成頻繁的進入全局隊列和移出全局隊列,從而下降應用程序的性能。基於此緣由,線程池引擎爲每一個線程引入了局部隊列。
- 線程的局部隊列爲咱們帶來兩個性能優點:任務內聯化(task inlining)和工做竊取機制。
1) 任務內聯化(task inlining)----活用頂層任務工做線程
咱們用一個示例來講明:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
static
void
Main(
string
[] args)
{
Task headTask=
new
Task(() =>
{
DoSomeWork(
null
);
});
headTask.Start();
Console.Read();
}
private
static
void
DoSomeWork(
object
obj)
{
Console.WriteLine(
"任務headTask運行在線程「{0}」上"
,
Thread.CurrentThread.ManagedThreadId);
var
taskTop =
new
Task(() =>
{
Thread.Sleep(500);
Console.WriteLine(
"任務taskTop運行在線程「{0}」上"
,
Thread.CurrentThread.ManagedThreadId);
});
var
taskCenter =
new
Task(() =>
{
Thread.Sleep(500);
Console.WriteLine(
"任務taskCenter運行在線程「{0}」上"
,
Thread.CurrentThread.ManagedThreadId);
});
var
taskBottom =
new
Task(() =>
{
Thread.Sleep(500);
Console.WriteLine(
"任務taskBottom運行在線程「{0}」上"
,
Thread.CurrentThread.ManagedThreadId);
});
taskTop.Start();
taskCenter.Start();
taskBottom.Start();
Task.WaitAll(
new
Task[] { taskTop, taskCenter, taskBottom });
}
|
結果:
分析:(目前內聯機制只有出如今等待任務場景)
這個示例,咱們從Main方法主線程中建立了一個headTask頂層任務並開啓。在headTask任務中又建立了三個嵌套任務並最後WaitAll() 這三個嵌套任務執行完成(嵌套任務安排在局部隊列)。此時出現的狀況就是headTask任務的線程被阻塞,而「任務內聯化」技術會使用阻塞的headTask的線程去執行局部隊列中的任務。由於減小了對額外線程需求,從而提高了程序性能。
局部隊列「一般」以LIFO的順序抽取任務並執行,而不是像全局隊列那樣使用FIFO順序。LIFO順序一般用有利於數據局部性,可以在犧牲一些公平性的狀況下提高性能。
數據局部性的意思是:運行最後一個到達的任務所需的數據都還在任何一個級別的CPU高速緩存中可用。因爲數據在高速緩存中任然是「熱的」,所以當即執行最後一個任務可能會得到性能提高。
2) 工做竊取機制----活用空閒工做線程
當一個工做線程的局部隊列中有不少工做項正在等待時,而存在一些線程卻保持空閒,這樣會致使CPU資源的浪費。此時任務調度器(TaskScheduler)會讓空閒的工做線程進入忙碌線程的局部隊列中竊取一個等待的任務,而且執行這個任務。
因爲局部隊列爲咱們帶來了性能提高,因此,咱們應儘量地使用TPL提供的服務(任務調度器(TaskScheduler)),而不是直接使用ThreadPool的方法。
任務並行Task
一個任務表示一個異步操做。任務運行的時候須要使用線程,但並非說任務取代了線程,理解這點很重要。事實上,在《異步編程:.NET4.X 數據並行》中介紹的System.Threading.Tasks.Parallel類構造的並行邏輯內部都會建立Task,而它們的並行和併發執行都是由底層線程支持的。任務和線程之間也沒有一對一的限制關係,通用語言運行時(CLR)會建立必要的線程來支持任務執行的需求。
- Task簡單的實例成員
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
public
class
Task : IThreadPoolWorkItem, IAsyncResult, IDisposable
{
public
Task(Action<
object
> action,
object
state
, CancellationToken cancellationToken,TaskCreationOptions creationOptions);
public
int
Id {
get
; }
public
TaskCreationOptions CreationOptions {
get
; }
public
TaskStatus Status {
get
; }
public
bool
IsCanceled {
get
; }
public
bool
IsFaulted {
get
; }
public
AggregateException Exception {
get
; }
#region IAsyncResult接口成員
private
bool
IAsyncResult.CompletedSynchronously {
get
;}
private
WaitHandleIAsyncResult.AsyncWaitHandle {
get
; }
public
object
AsyncState {
get
; }
public
bool
IsCompleted {
get
; }
#endregion
public
void
Dispose();
……
}
|
分析:
1) CancellationToken、IsCancel
對於長時間運行的計算限制操做來講,支持取消是一件很「棒」的事情。.NET 4.0提供了一個標準的取消操做模式。即經過使用CancellationTokenSource建立一個或多個取消標記CancellationToken(cancellationToken可在線程池中線程或 Task 對象之間實現協做取消),而後將此取消標記傳遞給應接收取消通知的任意數量的線程或Task對象。當調用CancellationToken關聯的CancellationTokenSource對象的Cancle()時,每一個取消標記(CancellationToken)上的IsCancellationRequested屬性將返回true。異步操做中能夠經過檢查此屬性作出任何適當響應。也可調用取消標記的ThrowIfCancellationRequested()方法來拋出OperationCanceledException異常。
更多關於CancellationToken與CancellationTokenSource的介紹及示例請看《協做式取消》….
在Task任務中實現取消,可使用如下幾種選項之一終止操做:
- i. 簡單地從委託中返回。在許多狀況下,這樣已足夠;可是,採用這種方式「取消」的任務實例會轉換爲RanToCompletion狀態,而不是 Canceled 狀態。
- ii. 建立Task時傳入CancellationToken標識參數,並調用關聯CancellationTokenSource對象的Cancel()方法:
a) 若是Task還未開始,那麼Task實例直接轉爲Canceled狀態。(注意,由於已經Canceled狀態了,因此不能再在後面調用Start())
b) (見示例:TaskOperations.Test_Cancel();)若是Task已經開始,在Task內部必須拋出OperationCanceledException異常(注意,只能存在OperationCanceledException異常,可優先考慮使用CancellationToken的ThrowIfCancellationRequested()方法),Task實例轉爲Canceled狀態。
若對拋出OperationCanceledException異常且狀態爲Canceled的Task進行等待操做(如:Wait/WaitAll),則會在Catch塊中捕獲到OperationCanceledException異常,可是此異常指示Task成功取消,而不是有錯誤的狀況。所以IsCancel爲true;IsFaulted爲false且Exception屬性爲null。
- iii. 對於使用TaskContinuationOptions枚舉值爲NotOn或OnlyOn建立的延續任務A,在其前面的任務結束狀態不匹配時,延續任務A將轉換爲Canceled狀態,而且不會運行。
2) TaskCreationOptions枚舉
定義任務建立、調度和執行的一些可選行爲。
None |
指定應使用默認行爲。 |
PreferFairness |
較早安排的任務將更可能較早運行,而較晚安排運行的任務將更可能較晚運行。(Prefer:更喜歡 ; Fair:公平的) |
LongRunning |
該任務須要很長時間運行,所以,調度器能夠對這個任務使用粗粒度的操做(默認TaskScheduler爲任務建立一個專用線程,而不是排隊讓一個線程池線程來處理,可經過在延續任務中訪問:Thread.CurrentThread.IsThreadPoolThread屬性判別)。好比:若是任務可能須要好幾秒的時間運行,那麼就使用這個參數。相反,若是任務只須要不到1秒鐘的時間運行,那麼就不該該使用這個參數。 |
AttachedToParent |
指定此枚舉值的Task,其內部建立的Task或經過ContinueWith()建立的延續任務都爲子任務。(父級是頂層任務) |
DenyChildAttach |
若是嘗試附加子任務到建立的任務,指定System.InvalidOperationException將被引起。 |
HideScheduler |
建立任務的執行操做將被視爲TaskScheduler.Default默認計劃程序。 |
3) IsCompleted
Task實現了IAsyncResult接口。在任務處於如下三個最終狀態之一時IsCompleted返回 true:RanToCompletion、 Faulted 或 Canceled。
4) TaskStatus枚舉
表示 Task 的生命週期中的當前階段。一個Task實例只會完成其生命週期一次,即當Task到達它的三種可能的最終狀態之一時,Task就結束並釋放。
可能的初始狀態 |
Created |
該任務已初始化,但還沒有被計劃。 |
WaitingForActivation |
只有在其它依賴的任務完成以後纔會獲得調度的任務的初始狀態。這種任務是使用定義延續的方法建立的。 |
WaitingToRun |
該任務已被計劃執行,但還沒有開始執行。 |
中間狀態 |
Running |
該任務正在運行,但還沒有完成。 |
WaitingForChildrenToComplete |
該任務已完成執行,正在隱式等待附加的子任務完成。 |
可能的最終狀態 |
RanToCompletion |
已成功完成執行的任務。 |
Canceled |
該任務已經過對其自身的CancellationToken引起OperationCanceledException異常 |
Faulted |
因爲未處理異常的緣由而完成的任務。 |
狀態圖以下:
5) Dispose()
儘管Task爲咱們實現了IDisposable接口,但依然不推薦你主動調用Dispose()方法,而是由系統終結器進行清理。緣由:
a) Task調用Dispose()主要釋放的資源是WaitHandle對象。
b) .NET4.5 對.NET4.0 中提出的Task進行過大量的優化,讓其儘可能再也不依賴WaitHandle對象(eg:.NET4.0種Task的WaitAll()/WaitAny()的實現依賴於WaitHandle)。
c) 在使用Task時,大多數狀況下找不到一個好的釋放點,保證該Task已經完成而且沒有被其餘地方在使用。
d) Task.Dispose()方法在「.NET Metro風格應用程序」框架所引用的程序集中甚至並不存在(即此框架中Task沒有實現IDisposable接口)。
更詳細更專業的Dispose()討論請看《.NET4.X並行任務Task須要釋放嗎?》…
- Task的實例方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public
TaskAwaiter GetAwaiter();
public
ConfiguredTaskAwaitable ConfigureAwait(
bool
continueOnCapturedContext);
public
void
RunSynchronously(TaskScheduler scheduler);
public
void
Start(TaskScheduler scheduler);
public
bool
Wait(
int
millisecondsTimeout, CancellationToken cancellationToken);
public
Task ContinueWith(Action<Task,
object
> continuationAction,
object
state
, CancellationToken cancellationToken
, TaskContinuationOptions continuationOptions, TaskScheduler scheduler);
public
Task<TResult>ContinueWith<TResult>(
Func<Task,
object
, TResult> continuationFunction
,
object
state,CancellationToken cancellationToken
, TaskContinuationOptions continuationOptions, TaskScheduler scheduler);
……
|
分析:
1) TaskContinuationOptions
在建立一個Task做爲另外一個Task的延續時,你能夠指定一個TaskContinuationOptions參數,這個參數能夠控制延續另外一個任務的任務調度和執行的可選行爲。
None |
默認狀況下,完成前面的任務以後「都」將安排運行延續任務,而不考慮前面任務的最終TaskStatus。 |
AttachedToParent |
對延續任務指定此枚舉值,表示該延續任務內部建立的新Task或經過ContinueWith()建立的延續任務都爲子任務。(父級是延續任務) |
PreferFairness LongRunning DenyChildAttach HideScheduler |
參考:TaskCreationOptions枚舉 |
LazyCancellation |
在延續取消的狀況下,防止延續的完成直到完成先前的任務。 |
NotOnRanToCompletion NotOnFaulted NotOnCanceled |
指定不該在延續任務前面的任務「已完成運行、引起了未處理異常、已取消」的狀況下安排延續任務。 |
此選項對多任務延續無效。 |
OnlyOnCanceled OnlyOnFaulted OnlyOnRanToCompletion |
指定只應在延續任務前面的任務「已取消、引起了未處理異常、已完成運行」的狀況下才安排延續任務。 |
ExecuteSynchronously |
指定應同步執行延續任務。指定此選項後,延續任務將在致使前面的任務轉換爲其最終狀態的相同線程上運行。 |
注意:
a) 若是使用默認選項TaskContinuationOptions.None,而且以前的任務被取消了,那麼延續任務任然會被調度並啓動執行。
b) 若是該條件在前面的任務準備調用延續時未獲得知足,則延續將直接轉換爲 Canceled 狀態,以後將沒法啓動。
c) 若是調用多任務延續(即:調用TaskFactory或TaskFactory<TResult>的靜態ContinueWhenAll和ContinueWhenAny方法)時,NotOn和OnlyOn六個標識或標識的組合都是無效的。也就是說,不管先驅任務是如何完成的,ContinueWhenAll和ContinueWhenAny都會執行延續任務。
d) TaskContinuationOptions.ExecuteSynchronously,指定同步執行延續任務。延續任務會使用前一個任務的數據,而保持在相同線程上執行就能快速訪問高速緩存中的數據,從而提高性能。此外,也可避免調度這個延續任務產生沒必要要的額外線程開銷。
若是在建立延續任務時已經完成前面的任務,則延續任務將在建立此延續任務的線程上運行。只應同步執行運行時間很是短的延續任務。
2) 開啓任務
只有Task處於TaskStatus.Created狀態時才能使用實例方法Start()。而且,只有在使用Task的公共構造函數構造的Task實例才能處於TaskStatus.Created狀態。
固然咱們還知道有其餘方式能夠建立Task並開啓任務,好比Task.Run()/Task.ContinueWith()/Task.Factory.StartNew()/TaskCompletionSource/異步方法(即便用async與await關鍵字的方法),可是這些方法返回的Task已經處於開啓狀態,即不能再調用Start()。更豐富更專業的討論請看《.NET4.X 並行任務中Task.Start()的FAQ》…
3) 延續任務ContinueWith
a) ContinueWith() 方法可建立一個根據TaskContinuationOptions參數限制的延續任務。能夠爲同一個Task定義多個延續任務讓它們並行執行。
好比,爲t1定義兩個並行延續任務t二、t3.
1
2
3
|
Task<
int
> t1 =
new
Task<
int
>(() => {
return
1; });
Task<
int
> t2 = t1.ContinueWith<
int
>(Work1,……);
Task<
int
> t3 = t1.ContinueWith<
int
>(Work1,……);
|
b) 調用Wait()方法和Result屬性會致使線程阻塞,極有可能形成線程池建立一個新線程,這增大了資源的消耗,並損害了伸縮性。能夠在延續任務中訪問這些成員,並作相應操做。
c) 對前面任務的引用將以參數形式傳遞給延續任務的用戶委託,以將前面任務的數據傳遞到延續任務中。
4) Wait()
一個線程調用Wait()方法時,系統會檢查線程要等待的Task是否已開始執行。
a) 若是是,調用Wait()的線程會阻塞,直到Task運行結束爲止。
b) 若是Task尚未開始執行,系統可能(取決於局部隊列的內聯機制)使用調用Wait()的線程來執行Task。若是發生這種狀況,那麼調用Wait()的線程不會阻塞;它會執行Task並馬上返回。
- i. 這樣作的好處在於,沒有線程會被阻塞,因此減小了資源的使用(由於不須要建立一個線程來替代被阻塞的線程),並提高了性能(由於不須要花時間建立一個線程,也沒有上下文切換)。
- ii. 但很差的地方在於,假如線程在調用Wait()前已經得到一個不可重入的線程同步鎖(eg:SpinLock),而Task試圖獲取同一個鎖,就會形成一個死鎖的線程!
5) RunSynchronously
可在指定的TaskScheduler或TaskScheduler.Current中同步運行 Task。即RunSynchronously()以後的代碼會阻塞到Task委託執行完畢。
示例以下:
1
2
3
4
5
6
7
8
9
10
11
|
Task task1 =
new
Task(() =>
{
Thread.Sleep(5000);
Console.WriteLine(
"task1執行完畢。"
);
});
task1.RunSynchronously();
Console.WriteLine(
"執行RunSynchronously()以後的代碼。"
);
|
- Task的靜態方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
public
static
int
? CurrentId{
get
; }
public
static
TaskFactory Factory {
get
; }
public
static
Task<TResult> FromResult<TResult>(TResult result);
public
static
Task Delay(
int
millisecondsDelay, CancellationToken cancellationToken);
public
static
Task Run(Action action, CancellationToken cancellationToken);
public
static
Task<TResult> Run<TResult>(Func<TResult> function, CancellationToken cancellationToken);
public
static
Task Run(Func<Task> function, CancellationToken cancellationToken);
public
static
Task<TResult> Run<TResult>(Func<Task<TResult>> function, CancellationToken cancellationToken);
public
static
bool
WaitAll(Task[] tasks, intmillisecondsTimeout, CancellationToken cancellationToken);
public
static
int
WaitAny(Task[] tasks,
int
millisecondsTimeout, CancellationToken cancellationToken);
public
static
Task WhenAll(IEnumerable<Task> tasks);
public
static
Task<TResult[]> WhenAll<TResult>(IEnumerable<Task<TResult>> tasks);
public
static
Task<Task> WhenAny(IEnumerable<Task> tasks);
public
static
Task<Task<TResult>> WhenAny<TResult>(IEnumerable<Task<TResult>> tasks);
public
static
YieldAwaitable Yield();
|
分析:
1) FromResult<TResult>(TResult result);
建立指定結果的、成功完成的Task<TResult>。咱們可使用此方法建立包含預先計算結果/緩存結果的 Task<TResult>對象,示例代碼或CachedDownloads.cs示例文件。
2) Delay
建立將在指定延遲後完成的任務,返回Task。能夠經過await或Task.Wait()來達到Thread.Sleep()的效果。儘管,Task.Delay() 比Thread.Sleep()消耗更多的資源,可是Task.Delay()可用於爲方法返回Task類型;或者根據CancellationToken取消標記動態取消等待。
Task.Delay()等待完成返回的Task狀態爲RanToCompletion;若被取消,返回的Task狀態爲Canceled。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
var
tokenSource =
new
CancellationTokenSource();
var
token = tokenSource.Token;
Task.Factory.StartNew(() => { Thread.Sleep(1000); tokenSource.Cancel(); });
Console.WriteLine(
"Begin taskDelay1"
);
Task taskDelay1 = Task.Delay(100000, token);
try
{
taskDelay1.Wait();
}
catch
(AggregateException ae)
{
foreach
(
var
v
in
ae.InnerExceptions)
Console.WriteLine(ae.Message +
" "
+ v.Message);
}
taskDelay1.ContinueWith((t) =>Console.WriteLine(t.Status.ToString()));
Thread.Sleep(100);
Console.WriteLine();
Console.WriteLine(
"Begin taskDelay2"
);
Task taskDelay2 = Task.Delay(1000);
taskDelay2.ContinueWith((t) =>Console.WriteLine(t.Status.ToString()));
|
- Task<TResult>:Task
Task<TResult>繼承自Task,表示一個能夠返回值的異步操做,提供Result只讀屬性用於訪問異步操做的返回值。該屬性會阻塞線程,直到Task執行完畢並返回值。
System.Threading.Tasks.TaskFactory
- 設置共用\默認的參數
經過TaskFactory對象提供的Scheduler、CancellationToken、CreationOption和ContinuationOptions屬性能夠爲Task設置共用\默認的參數,以便快捷的建立Task或延續任務。影響StartNew()、ContinueWhenAll()|ContinueWhenAny()、FromAsync()方法的默認參數設置。
- StartNew()
Task.Factory.StartNew()可快速建立一個Task而且開啓任務。代碼以下:
1
|
var
t = Task.Factory.StartNew(someDelegate);
|
這等效於:
1
2
|
var
t =
new
Task(someDelegate);
t.Start();
|
表現方面,前者更高效。Start()採用同步方式運行以確保任務對象保持一致的狀態即便是同時調用屢次Start(),也可能只有一個調用會成功。相比之下,StartNew()知道沒有其餘代碼能同時啓動任務,由於在StartNew()返回以前它不會將建立的Task引用給任何人,因此StartNew()不須要採用同步方式執行。更豐富更專業的討論請看《.NET4.X 並行任務中Task.Start()的FAQ》…
- ContinueWhenAll()
1
2
3
|
public
Task ContinueWhenAll(Task[] tasks, Action<Task[]> continuationAction
, CancellationToken cancellationToken
, TaskContinuationOptions continuationOptions, TaskScheduler scheduler);
|
建立一個延續 Task 或延續 Task<TResult>,它將在提供的一組任務完成後立刻開始。延續任務操做委託接受一個Task[]數組作參數。
- ContinueWhenAny()
1
2
3
|
public
Task ContinueWhenAny(Task[] tasks, Action<Task> continuationAction
, CancellationToken cancellationToken
, TaskContinuationOptions continuationOptions, TaskScheduler scheduler);
|
建立一個延續 Task 或延續 Task<TResult>,它將在提供的組中的任何一個任務完成後立刻開始。延續任務操做委託接受一個 Task 作參數。
- 經過Task.TaskFactory.FromAsync() 實例方法,咱們能夠將APM轉化爲TAP。示例見此文的後面小節「AMP轉化爲TAP和EAP轉化爲TAP」。
System.Threading.Tasks.TaskScheduler
TaskScheduler表示一個處理將任務排隊到線程中的底層工做對象。TaskScheduler一般有哪些應用呢?
- TaskScheduler是抽象類,能夠繼承它實現本身的任務調度計劃。如:默認調度程序ThreadPoolTaskScheduler、與SynchronizationContext.Current關聯的SynchronizationContextTaskScheduler。
- 由TaskScheduler.Default獲取默認調度程序ThreadPoolTaskScheduler。
- 由TaskScheduler.Current獲取當前任務執行的TaskScheduler。
- 由 TaskScheduler.TaskSchedulerFromCurrentSynchronizationContext() 方法獲取與SynchronizationContext.Current關聯的SynchronizationContextTaskScheduler,SynchronizationContextTaskScheduler上的任務都會經過SynchronizationContext.Post()在同步上下文中進行調度。一般用於實現跨線程更新控件。
- 經過MaximumConcurrencyLevel設置任務調度計劃能支持的最大併發級別。
- 經過UnobservedTaskException事件捕獲未被觀察到的異常。
System.Threading.Tasks.TaskExtensions
提供一組用於處理特定類型的 Task 實例的靜態方法。將特定Task實例進行解包操做。
1
2
3
4
5
|
public
static
class
TaskExtensions
{
public
static
Task<TResult> Unwrap<TResult>(
this
Task<Task<TResult>> task);
public
static
Task Unwrap(
this
Task<Task> task);
}
|
AMP轉化爲TAP和EAP轉化爲TAP
- AMP轉化爲TAP
經過Task.TaskFactory.FromAsync() 實例方法,咱們能夠將APM轉化爲TAP。
注意點:
1) FromAsync方法返回的任務具備WaitingForActivation狀態,並將在建立該任務後的某一時間由系統啓動。若是嘗試在這樣的任務上調用 Start,將引起異常。
2) 轉化的APM異步模型必須符合兩個模式:
a) 接受Begin***和End***方法。此時要求Begin***方法簽名的委託必須是AsyncCallback以及 End***方法只接受IAsyncResult一個參數。此模式AsyncCallback回調由系統自動生成,主要工做是調用End***方法。
1
2
3
4
|
public
Task<TResult> FromAsync<TArg1, TResult>(
Func<TArg1, AsyncCallback,
object
, IAsyncResult> beginMethod
, Func<IAsyncResult, TResult> endMethod, TArg1 arg1
,
object
state, TaskCreationOptions creationOptions);
|
b) 接受IAsyncResult對象以及End***方法。此時Begin***方法的簽名已經可有可無只要(即:此模式支持傳入自定義回調委託)能返回IAsyncResult的參數以及 End***方法只接受IAsyncResult一個參數。
1
2
|
public
Task<TResult> FromAsync<TResult>(IAsyncResult asyncResult
, Func<IAsyncResult, TResult> endMethod);
|
3) 固然,咱們有時須要給客戶提供統一的 Begin***() 和 End***() 調用方式,咱們能夠直接使用Task從零開始構造APM。即:在 Begin***() 建立並開啓任務,並返回Task。由於Task是繼承自IAsyncResult接口的,因此咱們能夠將其傳遞給 End***() 方法,並在此方法裏面調用Result屬性來等待任務完成。
4) 對於返回的Task,能夠隨時以任務延續的形式註冊回調。
如今將在《APM異步編程模型》博文中展示的示例轉化爲TAP模式。關鍵代碼以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public
Task<
int
> CalculateAsync<TArg1, TArg2>(
Func<TArg1, TArg2, AsyncCallback,
object
, IAsyncResult> beginMethod
, AsyncCallback userCallback, TArg1 num1, TArg2 num2,
object
asyncState)
{
IAsyncResult result = beginMethod(num1, num2, userCallback, asyncState);
return
Task.Factory.FromAsync<
int
>(result
, EndCalculate, TaskCreationOptions.None);
}
public
Task<
int
> CalculateAsync(
int
num1,
int
num2,
object
asyncState)
{
return
Task.Factory.FromAsync<
int
,
int
,
int
>(BeginCalculate, EndCalculate
, num1, num2, asyncState, TaskCreationOptions.None);
}
|
- EAP轉化爲TAP
咱們可使用TaskCompletionSource<TResult>實例將EAP操做表示爲一個Task<TResult>。
TaskCompletionSource<TResult>表示未綁定委託的Task<TResult>的製造者方,並經過TaskCompletionSource<TResult>.Task屬性獲取由此Tasks.TaskCompletionSource<TResult>建立的Task<TResult>。
注意,TaskCompletionSource<TResult>建立的任何任務將由TaskCompletionSource啓動,所以,用戶代碼不該在該任務上調用 Start()方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public
class
TaskCompletionSource<TResult>
{
public
TaskCompletionSource();
public
TaskCompletionSource(
object
state, TaskCreationOptions creationOptions);
public
Task<TResult> Task {
get
; }
public
void
SetCanceled();
public
bool
TrySetCanceled();
public
void
SetException(Exception exception);
public
void
SetException(IEnumerable<Exception> exceptions);
public
bool
TrySetException(Exception exception);
public
bool
TrySetException(IEnumerable<Exception> exceptions);
public
bool
TrySetResult(TResult result);
……
}
|
如今我將在《基於事件的異步編程模式(EAP)》博文中展示的BackgroundWorker2組件示例轉化爲TAP模式。
咱們須要修改地方有:
1) 建立一個TaskCompletionSource<int>實例tcs;
2) 爲tcs.Task返回的任務建立延續任務,延續任務中根據前面任務的IsCanceled、IsFaulted、Result等成員作邏輯;
3) Completed事件,在這裏面咱們將設置返回任務的狀態。
關鍵代碼以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
tcs =
new
TaskCompletionSource<
int
>();
worker2.RunWorkerCompleted += RunWorkerCompleted;
tcs.Task.ContinueWith(t =>
{
if
(t.IsCanceled)
MessageBox.Show(
"操做已被取消"
);
else
if
(t.IsFaulted)
MessageBox.Show(t.Exception.GetBaseException().Message);
else
MessageBox.Show(String.Format(
"操做已完成,結果爲:{0}"
, t.Result));
}, TaskContinuationOptions.ExecuteSynchronously);
worker2.RunWorkerAsync();
private
void
RunWorkerCompleted(
object
sender, RunWorkerCompletedEventArgs e)
{
if
(e.Error !=
null
)
tcs.SetException(e.Error);
else
if
(e.Cancelled)
tcs.SetCanceled();
else
tcs.SetResult((
int
)e.Result);
worker2.RunWorkerCompleted -= RunWorkerCompleted;
}
|
使用關鍵字async和await實現異步方法
在C#5.0中引入了async和await關鍵字,能夠方便咱們使用順序結構流(即不用回調)來實現異步編程,大大下降了異步編程的複雜程度。(vs2010打 Visual Studio Async CTP for VS2010補丁能夠引入關鍵字」async」和」await」的支持,可是得不到.net4.5新增API的支持)
異步方法的實現原理
異步方法不須要多線程,由於一個異步方法並非運行在一個獨立的線程中的。
異步方法運行在當前同步上下文中,只有激活的時候才佔用當前線程的時間。
異步模型採用時間片輪轉來實現。
異步方法的參數和返回值
異步方法的參數:
不能使用「ref」參數和「out」參數,可是在異步方法內部能夠調用含有這些參數的方法
異步方法的返回類型:
Task<TResult>:Tresult爲異步方法的返回值類型。
Task:異步方法沒有返回值。
void:主要用於事件處理程序(不能被等待,沒法捕獲異常)。異步事件一般被認爲是一系列異步操做的開始。使用void返回類型不須要await,並且調用void異步方法的函數不會捕獲方法拋出的異常。(異步事件中使用await,假若等待的任務由有異常會致使拋出「調用的目標發生了異常」。固然你能夠在異步事件中調用另外一個有返回值的異步方法)
異步方法的命名規範
異步方法的方法名應該以Async做爲後綴
事件處理程序,基類方法和接口方法,能夠忽略此命名規範:例如: startButton_Click不該重命名爲startButton_ClickAsync
async和await關鍵字不會致使其餘線程的建立,執行異步方法的線程爲其調用線程。而異步方法旨在成爲非阻塞操做,即當await等待任務運行時,異步方法會將控制權轉移給異步方法外部,讓其不受阻塞的繼續執行,待await等待的任務執行完畢再將控制權轉移給await處,繼續執行異步方法後續的代碼。
- 咱們可經過下圖來明白異步方法的構建和異步方法的執行流程。(代碼詳見我提供的示例程序async_await_method項目)
須要注意的一個問題:被「async」關鍵字標記的方法的調用都會強制轉變爲異步方式嗎?
不會,當你調用一個標記了」async」關鍵字的方法,它會在當前線程以同步的方式開始運行。因此,若是你有一個同步方法,它返回void而且你作的全部改變只是將其標記的「async」,這個方法調用依然是同步的。返回值爲Task或Task<TResult>也同樣。
方法用「async」關鍵字標記不會影響方法是同步仍是異步運行並完成,而是,它使方法可被分割成多個片斷,其中一些片斷可能異步運行,這樣這個方法可能異步完成。這些片斷界限就出如今方法內部顯示使用「await」關鍵字的位置處。因此,若是在標記了「async」的方法中沒有顯示使用「await」,那麼該方法只有一個片斷,而且將以同步方式運行並完成。
- 編譯器轉換
使用 async 關鍵字標記方法,會致使 C# 或 Visual Basic 編譯器使用狀態機從新編寫該方法的實現。藉助此狀態機,編譯器能夠在該方法中插入多箇中斷點,以便該方法能夠在不阻止線程的狀況下,掛起和恢復其執行。這些中斷點不會隨意地插入。它們只會在您明確使用 await 關鍵字的位置插入:
1
2
3
4
5
6
|
private
async
void
btnDoWork_Click(
object
sender, EventArgs e)
{
...
await someObject;
...
}
|
當您等待未完成的異步操做時,編譯器生成的代碼可確保與該方法相關的全部狀態(例如,局部變量)封裝並保留在堆中。而後,該函數將返回到調用程序,容許在其運行的線程中執行其餘任務。當所等待的異步操做在稍後完成時,該方法將使用保留的狀態恢復執行。
任何公開 await 模式的類型均可以進行等待。該模式主要由一個公開的 GetAwaiter()方法組成,該方法會返回一個提供 IsCompleted、OnCompleted 和 GetResult 成員的類型。當您編寫如下代碼時:
編譯器會生成一個包含 MoveNext 方法的狀態機類:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
private
class
FooAsyncStateMachine : IAsyncStateMachine
{
int
$state;
TaskAwaiter $awaiter;
…
public
void
MoveNext()
{
switch
(
this
.$state)
{
…
case
2:
goto
Label2;
…
}
…
this
.$awaiter = someObject.GetAwaiter();
if
(!
this
.$awaiter.IsCompleted)
{
this
.$state = 2;
this
.$awaiter.OnCompleted(MoveNext);
return
;
Label2:
}
this
.$awaiter.GetResult();
…
}
}
|
在實例someObject上使用這些成員來檢查該對象是否已完成(經過 IsCompleted),若是未完成,則掛接一個續體(經過 OnCompleted),當所等待實例最終完成時,系統將再次調用 MoveNext 方法,完成後,來自該操做的任何異常將獲得傳播或做爲結果返回(經過 GetResult),並跳轉至上次執行中斷的位置。
- 自定義類型支持等待
若是但願某種自定義類型支持等待,咱們能夠選擇兩種主要的方法。
1) 一種方法是針對自定義的可等待類型手動實現完整的 await 模式,提供一個返回自定義等待程序類型的 GetAwaiter 方法,該等待程序類型知道如何處理續體和異常傳播等等。
2) 第二種實施該功能的方法是將自定義類型轉換爲Task,而後只需依靠對等待任務的內置支持來等待特殊類型。前文所展現的「EAP轉化爲TAP」正屬於這一類,關鍵代碼以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
private
async
void
btn_Start_Click(
object
sender, EventArgs e)
{
this
.progressBar1.Value = 0;
tcs =
new
TaskCompletionSource<
int
>();
worker2.RunWorkerCompleted += RunWorkerCompleted;
tcs.Task.ContinueWith(t =>
{
if
(t.IsCanceled)
MessageBox.Show(
"操做已被取消"
);
else
if
(t.IsFaulted)
MessageBox.Show(t.Exception.GetBaseException().Message);
else
MessageBox.Show(String.Format(
"操做已完成,結果爲:{0}"
, t.Result));
}, TaskContinuationOptions.ExecuteSynchronously);
worker2.RunWorkerAsync();
}
|
處理TAP中的異常
在任務拋出的未處理異常都封裝在System.AggregateException對象中。這個對象會存儲在方法返回的Task或Task<TResult>對象中,須要經過訪問Wait()、Result、Exception成員才能觀察到異常。(因此,在訪問Result以前,應先觀察IsCanceled和IsFaulted屬性)
- AggregateException對象的三個重要成員
1) InnerExceptions屬性
獲取致使當前異常的System.Exception實例的只讀集合(即,ReadOnlyCollection<Exception>)。不要將其與基類Exception提供的InnerException屬性混淆。
2) Flatten() 方法
遍歷InnerExceptions異常列表,若列表中包含類型爲AggregateException的異常,就移除全部嵌套的AggregateException,直接返回其真真的異常信息(效果以下圖)。
1) Handle(Func<Exception, bool> predicate)方法
它爲AggregateException中包含的每一個異常都調用一個回調方法。而後,回調方法能夠爲每一個異常決定如何對其進行處理,回調返回true表示異常已經處理,返回false表示沒有。在調用Handle以後,若是至少有一個異常沒有處理,就建立一個新的AggregateException對象,其中只包含未處理的異常,並拋出這個新的AggregateException對象。
好比:將任何OperationCanceledException對象都視爲已處理。其餘任何異常都形成拋出一個新的AggregateException,其中只包含未處理的異常。
1
2
3
4
5
|
try
{……}
catch
(AggregateException ae)
{
ae.Handle(e => e
is
OperationCanceledException);
}
|
- 父任務生成了多個子任務,而多個子任務都拋出了異常
1) 嵌套子任務
1
2
3
4
5
6
7
8
9
10
|
Task t4 = Task.Factory.StartNew(() =>
{
Task.Factory.StartNew(() => {
throw
new
Exception(
"子任務Exception_1"
); }
, TaskCreationOptions.AttachedToParent);
Task.Factory.StartNew(() => {
throw
new
Exception(
"子任務Exception_2"
); }
, TaskCreationOptions.AttachedToParent);
throw
new
Exception(
"父任務Exception"
);
});
|
對於「嵌套子任務」中子任務的異常都會包裝在父任務返回的Task或Task<TResult>對象中。如此例子中 t4.Exception.InnerExceptions的Count爲3。
對於子任務返回的異常類型爲包裝過的AggregateException對象,爲了不循環訪問子任務異常對象的InnerExceptions才能獲取真真的異常信息,可使用上面提到的Flatten() 方法移除全部嵌套的AggregateExceprion。
2) Continue子任務
1
2
3
4
5
6
7
8
9
10
11
12
13
|
Task t1 = Task.Factory.StartNew(() =>
{
Thread.Sleep(500);
throw
new
Exception(
"父任務Exception"
);
}, TaskCreationOptions.AttachedToParent);
Task t2 = t1.ContinueWith((t) =>
{
throw
new
Exception(
"子任務Exception_1"
);
});
Task t3 = t1.ContinueWith((t) =>
{
throw
new
Exception(
"子任務Exception_2"
);
});
|
對於「Continue子任務」中的子任務其異常與父任務是分離的,各自包裝在本身返回的Task或 Task<TResult>對象中。如此示例 t一、t二、t3 的Exception.InnerExceptions的Count都爲1。
- TaskScheduler的UnobservedTaskException事件
假如你一直不訪問Task的Wait()、Result、Exception成員,那麼你將永遠注意不到這些異常的發生。爲了幫助你檢測到這些未處理的異常,能夠向TaskScheduler對象的UnobservedTaskException事件註冊回調函數。每當一個Task被垃圾回收時,若是存在一個沒有注意到的異常,CLR的終結器線程會引起這個事件。
可在事件回調函數中調用UnobservedTaskExceptionEventArgs對象的SetObserved() 方法來指出已經處理好了異常,從而阻止CLR終止線程。然而並不推薦這麼作,寧願終止進程也不要帶着已經損壞的狀態繼續運行。
示例代碼:(要監控此代碼必須在GC.Collect();和事件裏兩個地方進行斷點)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
TaskScheduler.UnobservedTaskException += (s, e) =>
{
e.SetObserved();
};
Task.Factory.StartNew(() =>
{
throw
new
Exception();
});
Thread.Sleep(100);
GC.Collect();
GC.WaitForPendingFinalizers();
|
- 返回void的async「異步方法」中的異常
咱們已經知道返回Task或Task<TResult>對象的任務中拋出的異常會隨着返回對象一塊兒返回,可經過Exception屬性獲取。這對於返回Task或Task<TResult>對象的「異步方法」狀況也是同樣。
然而對於返回void的「異步方法」,方法中拋出的異常會直接致使程序奔潰。
1
2
3
4
|
public
static
async
void
Test_void_async_Exception()
{
throw
new
Exception();
}
|
另外,咱們還要特別注意lambda表達式構成的「異步方法」,如:
1
|
Enumerable.Range(0, 3).ToList().ForEach(async (i) => {
throw
new
Exception(); });
|
本博文到此結束,我相信你看累了,其實我也寫了好久…好久…,寫完此文,個人「異步編程系列」也算有頭有尾了(還會繼續擴充)。本博文主要介紹了Task的重要API、任務的CLR線程池引擎、TaskFactory對象、TaskScheduler對象、TaskExtensions對象、AMP轉化爲TAP和EAP轉化爲TAP、使用關鍵字async和await實現異步方法以及自定義類型支持等待、處理TAP中的異常。
感謝你的觀看,若是對你有幫助,還請多多推薦……
===================================================================
本篇博文基於.NET4.5中TPL所寫。對於.NET4.0中TPL會有些差別,如有園友知道差別還請告知,我這邊作個記錄方便你們也方便本身。
一、.NET4.0中TPL未觀察到的異常會在GC回收時終止進程。(園友:YamatAmain,討論見21-26樓)
===================================================================
推薦閱讀:
異步性能:瞭解 Async 和 Await 的成本-----有講解到使用Task.ConfigureAwait(false)來避免捕獲原上下文來提高性能。
關於async與await的FAQ -----詳細講解了await和async的做用和意義,以及什麼是可等待對象、等待者……(此文可幫助你解決80%關於await和async關鍵字的疑惑)
深刻探究 WinRT 和 await -----基於WinRT平板win8系統,講解了異步功能API、TAP、編譯器轉換……
參考資料:MSDN
書籍:《CLR via C#(第三版)》
書籍:《C# 並行編程高級教程:精通.NET 4 Parallel Extensions》
一. FrameWork 4.0以前的線程世界
在.NET FrameWork 4.0以前,若是咱們使用線程。通常有如下幾種方式:
- 使用System.Threading.Thread 類,調用實例方法Start()開啓一個新線程,調用Abort()方法來提早終止線程。
- 使用System.Threading.ThreadPool類,調用靜態方法QueueUserWorkItem(),將方法放入線程池隊列,線程池來控制調用。
- 使用BeginInvoke,EndInvoke,BeginRead,EnRead,BeginWrite,EndWrite等一系列的異步方法。
- 使用System.ComponentModel.BackgroundWorker控件,調用實例方法RunWorkerAsync(),開啓一個新線程。
二. .Net 傳統異步編程概述
- 異步編程模型 (APM),在該模型中異步操做由一對 Begin/End 方法(如 FileStream.BeginRead 和 Stream.EndRead)表示。
- 基於事件的異步模式 (EAP),在該模式中異步操做由名爲「操做名稱Async」和「操做名稱Completed」的方法/事件對(例如 WebClient.DownloadStringAsync 和 WebClient.DownloadStringCompleted)表示。 (EAP 是在 .NET Framework 2.0 版中引入的,在silverlight或者wpf變成中常常用到)。
三. Task 的優勢以及功能
- 在任務啓動後,能夠隨時以任務延續的形式註冊回調。
- 經過使用 ContinueWhenAll 和 ContinueWhenAny 方法或者 WaitAll 方法或 WaitAny 方法,協調多個爲了響應 Begin_ 方法而執行的操做。
- 在同一 Task 對象中封裝異步 I/O 綁定和計算綁定操做。
- 監視 Task 對象的狀態。
- 使用 TaskCompletionSource 將操做的狀態封送到 Task 對象。
四. TASK的用法
請參考這篇文章
五. 使用 Task 封裝常見的異步編程模式
- 使用Task封裝APM異步編程模式。C#5.0中提供的async和await使異步編程更簡單。await在msdn的解釋是「運算符應用於一個異步方法的任務掛起方法的執行,直到等待任務完成。 任務表示正在進行的工做。」它返回的結果是Task和Task<TResult>.下面咱們就用一個demo具體解析:
private async void Init()
{
//部分代碼省略
var orgs = await _serviceClient.GetAllOrganizationTaskAsync();
}
//經過WCF調用,採起分佈類,名稱同樣。
public partial class ServiceClient
{
public Task<ObservableCollection<Organization>> GetAllOrganizationTaskAsync()
{
//Task 封裝APM
return Task<ObservableCollection<Organization>>.Factory.FromAsync(this.Channel.BeginGetAllOrganization, this.Channel.EndGetAllOrganization, null);
}
}
- 使用 Task 對象封裝 EPM 異步模式 。 這種模式從 .Net 2.0 開始出現, 同時在 Silverlight 中大量出現, 這種異步模式以 「操做名稱Async」 函數和 「操做名稱Completed」 事件成對出現爲特徵。常見的操做有使用lamda表達式;或者使用+=「操做名稱Completed」(tip:若是使用lamda表達式,沒法回收資源,若是同時調用多個,數據會亂套,建議若是非要用,最好用+=「操做名稱Completed」,在操做名稱Completed事件中再-=「操做名稱Completed」)。
關鍵字:TaskCompletionSource,簡單理解委託給task屬性,利用task來操做。
var source = new TaskCompletionSource<string>();
var webClient = new WebClient();
webClient.DownloadStringCompleted += (sender, args) => {
if (args.Cancelled) {
source.SetCanceled();
return;
}
if (args.Error != null) {
source.SetException(args.Error);
return;
}
source.SetResult(args.Result);
};
webClient.DownloadStringAsync(new Uri(UrlToTest, UriKind.Absolute), null);
source.Task.Wait();
var result = source.Task.Result;
4種異步調用的模式,分爲「等待」和「回調」兩大類。四種方法,我在代碼中都進行了詳細的註釋,這裏不羅嗦了,直接用代碼說明吧
第一種方法:BeginEnvoke EndEnvoke方法,屬於「等待」類。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace 異步調用實現方法彙總
{
///
/// 異步調用方法總結:
/// 1.BeginEnvoke EndEnvoke
/// 當使用BeginInvoke異步調用方法時,若是方法未執行完,EndInvoke方法就會一直阻塞,直到被調用的方法執行完畢
///
class Program
{
public delegate void PrintDelegate(string s);
static void Main(string[] args)
{
PrintDelegate printDelegate = Print;
Console.WriteLine("主線程");
IAsyncResult result= printDelegate.BeginInvoke("Hello World.", null, null);
Console.WriteLine("主線程繼續執行...");
//當使用BeginInvoke異步調用方法時,若是方法未執行完,EndInvoke方法就會一直阻塞,直到被調用的方法執行完畢
printDelegate.EndInvoke(result);
Console.WriteLine("Press any key to continue...");
Console.ReadKey(true);
}
public static void Print(string s)
{
Console.WriteLine("異步線程開始執行:"+s);
Thread.Sleep(5000);
}
}
}
須要注意的地方,代碼中都有註明了,程序運行結果以下:
![](http://static.javashuo.com/static/loading.gif)
第二種方法:WaitOne。一樣屬於「等待」類。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace 異步調用實現方法彙總2
{
///
/// 異步調用方法總結:
/// 2.WaitOne
/// 能夠看到,與EndInvoke相似,只是用WaitOne函數代碼了EndInvoke而已。
///
class Program
{
public delegate void PrintDelegate(string s);
static void Main(string[] args)
{
PrintDelegate printDelegate = Print;
Console.WriteLine("主線程");
IAsyncResult result = printDelegate.BeginInvoke("Hello World.", null, null);
Console.WriteLine("主線程繼續執行...");
result.AsyncWaitHandle.WaitOne(-1, false);
Console.WriteLine("Press any key to continue...");
Console.ReadKey(true);
}
public static void Print(string s)
{
Console.WriteLine("異步線程開始執行:" + s);
Thread.Sleep(5000);
}
}
}
須要注意的地方,代碼中都有註明了,程序運行結果以下:
![](http://static.javashuo.com/static/loading.gif)
第三種方法:輪詢。也是屬於「等待」類。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace 異步調用實現方法彙總3
{
///
/// 異步調用方法總結:
/// 3.輪詢
/// 以前提到的兩種方法,只能等下異步方法執行完畢,
/// 在完畢以前沒有任何提示信息,整個程序就像沒有響應同樣,用戶體驗很差,
/// 能夠經過檢查IasyncResult類型的IsCompleted屬性來檢查異步調用是否完成,
/// 若是沒有完成,則能夠適時地顯示一些提示信息
///
class Program
{
public delegate void PrintDelegate(string s);
static void Main(string[] args)
{
PrintDelegate printDelegate = Print;
Console.WriteLine("主線程:"+Thread.CurrentThread.ManagedThreadId );
IAsyncResult result = printDelegate.BeginInvoke("Hello world.", null, null);
Console.WriteLine("主線程:" + Thread.CurrentThread.ManagedThreadId + ",繼續執行...");
while (!result.IsCompleted)
{
Console.WriteLine(".");
Thread.Sleep(500);
}
Console.WriteLine("主線程:" + Thread.CurrentThread.ManagedThreadId + " Press any key to continue...");
Console.ReadKey(true);
}
public static void Print(string s)
{
Console.WriteLine("當前線程:" + Thread.CurrentThread.ManagedThreadId + s);
Thread.Sleep(5000);
}
}
}
須要注意的地方,代碼中都有註明了,程序運行結果以下:
![](http://static.javashuo.com/static/loading.gif)
第四種方法:回調。固然屬於「回調」類。推薦!!!!
以前三種方法者在等待異步方法執行完畢後才能拿到執行的結果,期間主線程均處於等待狀態。回調和它們最大的區別是,在調用BeginInvoke時只要提供了回調方法,那麼主線程就沒必要要再等待異步線程工做完畢,異步線程在工做結束後會主動調用咱們提供的回調方法,並在回調方法中作相應的處理。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace 異步調用實現方法彙總4
{
///
/// 異步調用方法總結:
/// 4.回調
/// 以前三種方法者在等待異步方法執行完畢後才能拿到執行的結果,期間主線程均處於等待狀態。
/// 回調和它們最大的區別是,在調用BeginInvoke時只要提供了回調方法,那麼主線程就沒必要要再等待異步線程工做完畢,
/// 異步線程在工做結束後會主動調用咱們提供的回調方法,並在回調方法中作相應的處理,例如顯示異步調用的結果。
///
class Program
{
public delegate void PrintDelegate(string s);
static void Main(string[] args)
{
PrintDelegate printDelegate = Print;
Console.WriteLine("主線程.");
printDelegate.BeginInvoke("Hello world.", PrintComeplete, printDelegate);
Console.WriteLine("主線程繼續執行...");
Console.WriteLine("Press any key to continue...");
Console.ReadKey(true);
}
public static void Print(string s)
{
Console.WriteLine("當前線程:"+s);
Thread.Sleep(5000);
}
//回調方法要求
//1.返回類型爲void
//2.只有一個參數IAsyncResult
public static void PrintComeplete(IAsyncResult result)
{
(result.AsyncState as PrintDelegate).EndInvoke(result);
Console.WriteLine("當前線程結束." + result.AsyncState.ToString());
}
}
}
須要注意的地方,代碼中都有註明了,程序運行結果以下:
![](http://static.javashuo.com/static/loading.gif)
以上就是四種實現異步調用函數的四種方法,說的很清楚了,就寫這麼多~
C#異步編程
一、什麼是異步?
異步操做一般用於執行完成時間可能較長的任務,如打開大文件、鏈接遠程計算機或查詢數據庫。異步操做在主應用程序線程之外的線程中執行。應用程序調用方法異步執行某個操做時,應用程序可在異步方法執行其任務時繼續執行。
二、同步與異步的區別
同步(Synchronous):在執行某個操做時,應用程序必須等待該操做執行完成後才能繼續執行。
異步(Asynchronous):在執行某個操做時,應用程序可在異步操做執行時繼續執行。實質:異步操做,啓動了新的線程,主線程與方法線程並行執行。
三、異步和多線程的區別
咱們已經知道,異步的實質是開啓了新的線程。它與多線程的區別是什麼呢?
簡單的說就是:異步線程是由線程池負責管理,而多線程,咱們能夠本身控制,固然在多線程中咱們也可使用線程池。
就拿網絡扒蟲而言,若是使用異步模式去實現,它使用線程池進行管理。異步操做執行時,會將操做丟給線程池中的某個工做線程來完成。當開始I/O操做的時候,異步會將工做線程還給線程池,這意味着獲取網頁的工做不會再佔用任何CPU資源了。直到異步完成,即獲取網頁完畢,異步纔會經過回調的方式通知線程池。可見,異步模式藉助於線程池,極大地節約了CPU的資源。
注:DMA(Direct Memory Access)直接內存存取,顧名思義DMA功能就是讓設備能夠繞過處理器,直接由內存來讀取資料。經過直接內存訪問的數據交換幾乎能夠不損耗CPU的資源。在硬件中,硬盤、網卡、聲卡、顯卡等都有直接內存訪問功能。異步編程模型就是讓咱們充分利用硬件的直接內存訪問功能來釋放CPU的壓力。
二者的應用場景:
計算密集型工做,採用多線程。
IO密集型工做,採用異步機制。
四、異步應用
.NET Framework 的許多方面都支持異步編程功能,這些方面包括:
1)文件 IO、流 IO、套接字 IO。
2)網絡。
3)遠程處理信道(HTTP、TCP)和代理。
4)使用 ASP.NET 建立的 XML Web services。
5)ASP.NET Web 窗體。
6)使用 MessageQueue 類的消息隊列。
.NET Framework 爲異步操做提供兩種設計模式:
1)使用 IAsyncResult 對象的異步操做。
2)使用事件的異步操做。
IAsyncResult 設計模式容許多種編程模型,但更加複雜不易學習,可提供大多數應用程序都不要求的靈活性。可能的話,類庫設計者應使用事件驅動模型實現異步方法。在某些狀況下,庫設計者還應實現基於 IAsyncResult 的模型。
使用 IAsyncResult 設計模式的異步操做是經過名爲 Begin操做名稱和End操做名稱的兩個方法來實現的,這兩個方法分別開始和結束異步操做操做名稱。例如,FileStream 類提供 BeginRead 和 EndRead 方法來從文件異步讀取字節。這兩個方法實現了 Read 方法的異步版本。在調用 Begin操做名稱後,應用程序能夠繼續在調用線程上執行指令,同時異步操做在另外一個線程上執行。每次調用 Begin操做名稱 時,應用程序還應調用 End操做名稱來獲取操做的結果。Begin操做名稱 方法開始異步操做操做名稱並返回一個實現 IAsyncResult 接口的對象。 .NET Framework 容許您異步調用任何方法。定義與您須要調用的方法具備相同簽名的委託;公共語言運行庫將自動爲該委託定義具備適當簽名的 BeginInvoke 和 EndInvoke 方法。
IAsyncResult 對象存儲有關異步操做的信息。下表提供了有關異步操做的信息。
名稱 |
說明 |
AsyncState |
獲取用戶定義的對象,它限定或包含關於異步操做的信息。 |
AsyncWaitHandle |
獲取用於等待異步操做完成的 WaitHandle。 |
CompletedSynchronously |
獲取一個值,該值指示異步操做是否同步完成。 |
IsCompleted |
獲取一個值,該值指示異步操做是否已完 |
五、應用實例
案例1-讀取文件
一般讀取文件是一個比較耗時的工做,特別是讀取大文件的時候,常見的上傳和下載。可是咱們又不想讓用戶一直等待,用戶一樣能夠進行其餘操做,可使得系統有良好的交互性。這裏咱們寫了同步調用和異步調用來進行比較說明。
讀取文件類
using System;
using System.IO;
using System.Threading;
namespace AsynSample
{
class FileReader
{
/// <summary>
/// 緩存池
/// </summary>
private byte[] Buffer { get; set; }
/// <summary>
/// 緩存區大小
/// </summary>
public int BufferSize { get; set; }
public FileReader(int bufferSize)
{
this.BufferSize = bufferSize;
this.Buffer = new byte[BufferSize];
}
/// <summary>
/// 同步讀取文件
/// </summary>
/// <param name="path">文件路徑</param>
public void SynsReadFile(string path)
{
Console.WriteLine("同步讀取文件 begin");
using (FileStream fs = new FileStream(path, FileMode.Open))
{
fs.Read(Buffer, 0, BufferSize);
string output = System.Text.Encoding.UTF8.GetString(Buffer);
Console.WriteLine("讀取的文件信息:{0}",output);
}
Console.WriteLine("同步讀取文件 end");
}
/// <summary>
/// 異步讀取文件
/// </summary>
/// <param name="path"></param>
public void AsynReadFile(string path)
{
Console.WriteLine("異步讀取文件 begin");
//執行Endread時報錯,fs已經釋放,注意在異步中不能使用釋放須要的資源
//using (FileStream fs = new FileStream(path, FileMode.Open))
//{
// Buffer = new byte[BufferSize];
// fs.BeginRead(Buffer, 0, BufferSize, AsyncReadCallback, fs);
//}
if (File.Exists(path))
{
FileStream fs = new FileStream(path, FileMode.Open);
fs.BeginRead(Buffer, 0, BufferSize, AsyncReadCallback, fs);
}
else
{
Console.WriteLine("該文件不存在");
}
}
/// <summary>
///
/// </summary>
/// <param name="ar"></param>
void AsyncReadCallback(IAsyncResult ar)
{
FileStream stream = ar.AsyncState as FileStream;
if (stream != null)
{
Thread.Sleep(1000);
//讀取結束
stream.EndRead(ar);
stream.Close();
string output = System.Text.Encoding.UTF8.GetString(this.Buffer);
Console.WriteLine("讀取的文件信息:{0}", output);
}
}
}
}
測試用例
using System;
using System.Threading;
namespace AsynSample
{
class Program
{
static void Main(string[] args)
{
FileReader reader = new FileReader(1024);
//改成本身的文件路徑
string path = "C:\\Windows\\DAI.log";
Console.WriteLine("開始讀取文件了...");
//reader.SynsReadFile(path);
reader.AsynReadFile(path);
Console.WriteLine("我這裏還有一大灘事呢.");
DoSomething();
Console.WriteLine("終於完事了,輸入任意鍵,歇着!");
Console.ReadKey();
}
/// <summary>
///
/// </summary>
static void DoSomething()
{
Thread.Sleep(1000);
for (int i = 0; i < 10000; i++)
{
if (i % 888 == 0)
{
Console.WriteLine("888的倍數:{0}",i);
}
}
}
}
}
輸出結果:
同步輸出:
![](http://static.javashuo.com/static/loading.gif)
異步輸出:
![](http://static.javashuo.com/static/loading.gif)
結果分析:
若是是同步讀取,在讀取時,當前線程讀取文件,只能等到讀取完畢,才能執行如下的操做
而異步讀取,是建立了新的線程,讀取文件,而主線程,繼續執行。咱們能夠開啓任務管理器來進行監視。
案例二--基於委託的異步操做
系統自帶一些類具備異步調用方式,如何使得自定義對象也具備異步功能呢?
咱們能夠藉助委託來輕鬆實現異步。
說到BeginInvoke,EndInvoke就不得不停下來看一下委託的本質。爲了便於理解委託,我定義一個簡單的委託:
public delegate string MyFunc(int num, DateTime dt);
咱們再來看一下這個委託在編譯後的程序集中是個什麼樣的:
![](http://static.javashuo.com/static/loading.gif)
委託被編譯成一個新的類型,擁有BeginInvoke,EndInvoke,Invoke這三個方法。前二個方法的組合使用即可實現異步調用。第三個方法將以同步的方式調用。 其中BeginInvoke方法的最後二個參數用於回調,其它參數則與委託的包裝方法的輸入參數是匹配的。 EndInvoke的返回值與委託的包裝方法的返回值匹配。
異步實現文件下載:
using System;
using System.Text;
namespace AsynSample
{
/// <summary>
/// 下載委託
/// </summary>
/// <param name="fileName"></param>
public delegate string AysnDownloadDelegate(string fileName);
/// <summary>
/// 經過委託實現異步調用
/// </summary>
class DownloadFile
{
/// <summary>
/// 同步下載
/// </summary>
/// <param name="fileName"></param>
public string Downloading(string fileName)
{
string filestr = string.Empty;
Console.WriteLine("下載事件開始執行");
System.Threading.Thread.Sleep(3000);
Random rand = new Random();
StringBuilder builder =new StringBuilder();
int num;
for(int i=0;i<100;i++)
{
num = rand.Next(1000);
builder.Append(i);
}
filestr = builder.ToString();
Console.WriteLine("下載事件執行結束");
return filestr;
}
/// <summary>
/// 異步下載
/// </summary>
public IAsyncResult BeginDownloading(string fileName)
{
string fileStr = string.Empty;
AysnDownloadDelegate downloadDelegate = new AysnDownloadDelegate(Downloading);
return downloadDelegate.BeginInvoke(fileName, Downloaded, downloadDelegate);
}
/// <summary>
/// 異步下載完成後事件
/// </summary>
/// <param name="result"></param>
private void Downloaded(IAsyncResult result)
{
AysnDownloadDelegate aysnDelegate = result.AsyncState as AysnDownloadDelegate;
if (aysnDelegate != null)
{
string fileStr = aysnDelegate.EndInvoke(result);
if (!string.IsNullOrEmpty(fileStr))
{
Console.WriteLine("下載文件:{0}", fileStr);
}
else
{
Console.WriteLine("下載數據爲空!");
}
}
else
{
Console.WriteLine("下載數據爲空!");
}
}
}
}
經過案例,咱們發現,使用委託可以很輕易的實現異步。這樣,咱們就能夠自定義本身的異步操做了。
System.Threading 命名空間
System.Threading 命名空間提供一些使得能夠進行多線程編程的類和接口。 除同步線程活動和數據訪問的類(Monitor、Interlocked、AutoResetEvent 等)以外,此命名空間還包含一個 ThreadPool 類(它使用戶可以使用系統提供的線程池)和一個 Timer 類(在線程池線程上執行回調方法)。 除了 System.Threading 命名空間中提供的功能以外,BackgroundWorker 類還提供一個簡單的基於事件的方法,以同步對主應用程序線程的訪問。
Thread 類
一個進程能夠建立一個或多個線程以執行與該進程關聯的部分程序代碼。 託管線程的執行單位是方法。 使用 ThreadStart 委託或ParameterizedThreadStart 委託能夠指定由線程執行的方法。 使用 ParameterizedThreadStart 委託能夠將數據做爲參數傳遞到線程過程。
您不須要本身建立線程。 BackgroundWorker 和 ThreadPool 類使您能夠經過一種面向任務的簡單方式來使用系統管理的後臺線程。 對於將結果返回用戶界面 (UI) 線程的後臺任務,最簡單的編程方法是使用 BackgroundWorker 類。 下表列出了有關各類併發編程的一些信息來源。
ThreadPool 類
提供一個線程池,該線程池可用於發送工做項、處理異步 I/O、表明其餘線程等待以及處理計時器。線程池經過爲應用程序提供一個由系統管理的輔助線程池,使您能夠更爲有效地使用線程。 一個線程監視排到線程池的若干個等待操做的狀態。 當一個等待操做完成時,線程池中的一個輔助線程就會執行對應的每一個進程都有一個線程池。 線程池的默認大小爲:每一個處理器 250 個輔助線程,再加上 1000 個 I/O 完成線程。
lock 關鍵字將語句塊標記爲臨界區,方法是獲取給定對象的互斥鎖,執行語句,而後釋放該鎖。
lock語句根本使用的就是Monitor.Enter和Monitor.Exit,也就是說lock(this)時執行Monitor.Enter(this),大括號結束時執行Monitor.Exit(this).他的意義在於什麼呢,對於任何一個對象來講,他在內存中的第一部分放置的是全部方法的地址,第二部分放着一個索引,他指向CLR中的SyncBlock Cache區域中的一個SyncBlock.什麼意思呢?就是說,當你執行Monitor.Enter(Object)時,若是object的索引值爲負數,就從SyncBlock Cache中選區一個SyncBlock,將其地址放在object的索引中。這樣就完成了以object爲標誌的鎖定,其餘的線程想再次進行Monitor.Enter(object)操做,將得到object爲正數的索引,而後就等待。直到索引變爲負數,即線程使用Monitor.Exit(object)將索引變爲負數。
使用lock須要注意的地方:
1.lock不能鎖定空值某一對象能夠指向Null,但Null是不須要被釋放的。(請參考:認識全面的null)
2.lock不能鎖定string類型,雖然它也是引用類型的。由於字符串類型被CLR「暫留」
這意味着整個程序中任何給定字符串都只有一個實例,就是這同一個對象表示了全部運行的應用程序域的全部線程中的該文本。所以,只要在應用程序進程中的任何位置處具備相同內容的字符串上放置了鎖,就將鎖定應用程序中該字符串的全部實例。所以,最好鎖定不會被暫留的私有或受保護成員。
3.lock鎖定的對象是一個程序塊的內存邊界
4.值類型不能被lock,由於前文標紅字的「對象被釋放」,值類型不是引用類型的
5.lock就避免鎖定public 類型或不受程序控制的對象。
例如,若是該實例能夠被公開訪問,則 lock(this) 可能會有問題,由於不受控制的代碼也可能會鎖定該對象。這可能致使死鎖,即兩個或更多個線程等待釋放同一對象。出於一樣的緣由,鎖定公共數據類型(相比於對象)也可能致使問題。
使用lock(this)的時候,類的成員變量的值可能會被不在臨界區的方法改值了
應用場景:常常會應用於防止多線程操做致使公用變量值出現不肯定的異常,用於確保操做的安全性
示例
// statements_lock2.cs
using System;
using System.Threading;
class Account
{
private Object thisLock = new Object();
int balance;
Random r = new Random();
public Account(int initial)
{
balance = initial;
}
int Withdraw(int amount)
{
// This condition will never be true unless the lock statement
// is commented out:
if (balance < 0)
{
throw new Exception("Negative Balance");
}
// Comment out the next line to see the effect of leaving out
// the lock keyword:
lock(thisLock)
{
if (balance >= amount)
{
Console.WriteLine("Balance before Withdrawal : " + balance);
Console.WriteLine("Amount to Withdraw : -" + amount);
balance = balance - amount;
Console.WriteLine("Balance after Withdrawal : " + balance);
return amount;
}
else
{
return 0; // transaction rejected
}
}
}
public void DoTransactions()
{
for (int i = 0; i < 100; i++)
{
Withdraw(r.Next(1, 100));
}
}
}
class Test
{
static void Main()
{
Thread[] threads = new Thread[10];
Account acc = new Account(1000);
for (int i = 0; i < 10; i++)
{
Thread t = new Thread(new ThreadStart(acc.DoTransactions));
threads[i] = t;
}
for (int i = 0; i < 10; i++)
{
threads[i].Start();
}
}
}
一. 爲何要lock,lock了什麼?
當咱們使用線程的時候,效率最高的方式固然是異步,即各個線程同時運行,其間不相互依賴和等待。但當不一樣的線程都須要訪問某個資源的時候,就須要同步機制了,也就是說當對同一個資源進行讀寫的時候,咱們要使該資源在同一時刻只能被一個線程操做,以確保每一個操做都是有效即時的,也即保證其操做的原子性。lock是C#中最經常使用的同步方式,格式爲lock(objectA){codeB} 。
lock(objectA){codeB} 看似簡單,實際上有三個意思,這對於適當地使用它相當重要:
1. objectA被lock了嗎?沒有則由我來lock,不然一直等待,直至objectA被釋放。
2. lock之後在執行codeB的期間其餘線程不能調用codeB,也不能使用objectA。
3. 執行完codeB以後釋放objectA,而且codeB能夠被其餘線程訪問。
二. lock(this)怎麼了?
咱們看一個例子:
- using System;
- using System.Threading;
- namespace Namespace1
- {
- class C1
- {
- private bool deadlocked = true;
- //這個方法用到了lock,咱們但願lock的代碼在同一時刻只能由一個線程訪問
- public void LockMe(object o)
- {
- lock (this)
- {
- while(deadlocked)
- {
- deadlocked = (bool)o;
- Console.WriteLine("Foo: I am locked :(");
- Thread.Sleep(500);
- }
- }
- }
- //全部線程均可以同時訪問的方法
- public void DoNotLockMe()
- {
- Console.WriteLine("I am not locked :)");
- }
- }
- class Program
- {
- static void Main(string[] args)
- {
- C1 c1 = new C1();
- //在t1線程中調用LockMe,並將deadlock設爲true(將出現死鎖)
- Thread t1 = new Thread(c1.LockMe);
- t1.Start(true);
- Thread.Sleep(100);
- //在主線程中lock c1
- lock (c1)
- {
- //調用沒有被lock的方法
- c1.DoNotLockMe();
- //調用被lock的方法,並試圖將deadlock解除
- c1.LockMe(false);
- }
- }
- }
複製代碼
在t1線程中,LockMe調用了lock(this), 也就是Main函數中的c1,這時候在主線程中調用lock(c1)時,必需要等待t1中的lock塊執行完畢以後才能訪問c1,即全部c1相關的操做都沒法完成,因而咱們看到連c1.DoNotLockMe()都沒有執行。
把C1的代碼稍做改動:
- class C1
- {
- private bool deadlocked = true;
- private object locker = new object();
- //這個方法用到了lock,咱們但願lock的代碼在同一時刻只能由一個線程訪問
- public void LockMe(object o)
- {
- lock (locker)
- {
- while(deadlocked)
- {
- deadlocked = (bool)o;
- Console.WriteLine("Foo: I am locked :(");
- Thread.Sleep(500);
- }
- }
- }
- //全部線程均可以同時訪問的方法
- public void DoNotLockMe()
- {
- Console.WriteLine("I am not locked :)");
- }
- }
複製代碼
此次咱們使用一個私有成員做爲鎖定變量(locker),在LockMe中僅僅鎖定這個私有locker,而不是整個對象。這時候從新運行程序,能夠看到雖然t1出現了死鎖,DoNotLockMe()仍然能夠由主線程訪問;LockMe()依然不能訪問,緣由是其中鎖定的locker尚未被t1釋放。
關鍵點:
1. lock(this)的缺點就是在一個線程(例如本例的t1)經過執行該類的某個使用"lock(this)"的方法(例如本例的LockMe())鎖定某對象以後, 致使整個對象沒法被其餘線程(例如本例的主線程)訪問 - 由於不少人在其餘線程(例如本例的主線程)中使用該類的時候會使用相似lock(c1)的代碼。
2. 鎖定的不只僅是lock段裏的代碼,鎖自己也是線程安全的。
3. 咱們應該使用不影響其餘操做的私有對象做爲locker。
4. 在使用lock的時候,被lock的對象(locker)必定要是引用類型的,若是是值類型,將致使每次lock的時候都會將該對象裝箱爲一個新的引用對象(事實上若是使用值類型,C#編譯器(3.5.30729.1)在編譯時就會給出一個錯誤)。
在迭代器塊中用於向枚舉數對象提供值或發出迭代結束信號。
yield return <expression>; yield break;
計算表達式並以枚舉數對象值的形式返回;expression 必須能夠隱式轉換爲迭代器的 yield 類型。
yield 語句只能出如今 iterator 塊中,該塊可用做方法、運算符或訪問器的體。
一個迭代器塊(iterator block)是一個可以產生有序的值序列的塊。迭代器塊和普通語句塊的區別就是其中出現的一個或多個yield語句。 yield return語句產生迭代的下一個值。 yield break語句表示迭代完成。
示例
using System;
using System.Collections;
public class List
{
public static IEnumerable Power(int number, int exponent)
{
int counter = 0;
int result = 1;
while (counter++ < exponent)
{
result = result * number;
yield return result;
}
}
static void Main()
{
// Display powers of 2 up to the exponent 8:
foreach (int i in Power(2, 8))
{
Console.Write("{0} ", i);
}
}
}
2 4 8 16 32 64 128 256
BackgroundWorker 類
在單獨的線程上運行操做。
BackgroundWorker 類容許您在單獨的專用線程上運行操做。 諸以下載和數據庫事務這樣的耗時操做會致使用戶界面中止響應。 若是您須要能進行響應的用戶界面,並且必須執行耗時操做,則可使用 BackgroundWorker 類方便地解決問題。
若要在後臺運行操做,請建立一個 BackgroundWorker。 能夠偵聽報告操做進度並在操做完成時發出信號的事件。
若要設置後臺操做,請爲 DoWork 事件添加事件處理程序。 在此事件處理程序中調用耗時的操做。 若要啓動後臺操做,請調用RunWorkerAsync 方法。 若要收到進度更新的通知,請處理 ProgressChanged 事件。 若要在操做完成時收到通知,請處理RunWorkerCompleted 事件。
若是後臺操做須要參數,請在調用 RunWorkerAsync 時給出參數。 在 DoWork 事件處理程序內部,能夠從DoWorkEventArgs.Argument 屬性中提取該參數。
有關 BackgroundWorker 的更多信息,請參見如何:使用後臺輔助線程。
BackgroundWorker 類
更新:2010 年 9 月
命名空間:
System.ComponentModel
程序集: System(在 System.dll 中)
[HostProtectionAttribute(SecurityAction.LinkDemand, SharedState = true)]
public class BackgroundWorker : Component
BackgroundWorker 類型公開如下成員。
BackgroundWorker 類容許您在單獨的專用線程上運行操做。 耗時的操做(以下載和數據庫事務)在長時間運行時可能會致使用戶界面 (UI) 彷佛處於中止響應狀態。 若是您須要能進行響應的用戶界面,並且面臨與這類操做相關的長時間延遲,則可使用 BackgroundWorker 類方便地解決問題。
若要在後臺執行耗時的操做,請建立一個 BackgroundWorker,偵聽那些報告操做進度並在操做完成時發出信號的事件。 能夠經過編程方式建立BackgroundWorker,也能夠將它從「工具箱」的「組件」選項卡中拖到窗體上。 若是在 Windows 窗體設計器中建立 BackgroundWorker,則它會出如今組件欄中,並且它的屬性會顯示在「屬性」窗口中。
若要爲後臺操做作好準備,請添加 DoWork 事件的事件處理程序。 在此事件處理程序中調用耗時的操做。 若要開始此操做,請調用RunWorkerAsync。 若要收到進度更新的通知,請處理 ProgressChanged 事件。 若要在操做完成時收到通知,請處理 RunWorkerCompleted 事件。
若是後臺操做須要參數,請在調用 RunWorkerAsync 時給出參數。 在 DoWork 事件處理程序內部,能夠從 DoWorkEventArgs.Argument 屬性中提取該參數。
有關 BackgroundWorker的更多信息,請參見如何:在後臺運行操做。
下面的代碼示例演示 BackgroundWorker 類異步執行耗時的基本知識。 下圖顯示輸出的示例。
要嘗試該代碼,可建立 Windows 窗體應用程序。 添加一個名爲 resultLabel 的 Label 控件並添加兩個名爲 startAsyncButton 和cancelAsyncButton 的 Button 控件。 建立這兩個按鈕的 Click 事件處理程序。 從工具箱中的「組件」選項卡中,添加命名爲 backgroundWorker1的 BackgroundWorker 組件。 建立 DoWork、 ProgressChanged 和 BackgroundWorker 的 RunWorkerCompleted 事件處理程序。 在窗體的代碼中,用下列代碼替換現有代碼。
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace BackgroundWorkerSimple
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = true;
}
private void startAsyncButton_Click(object sender, EventArgs e)
{
if (backgroundWorker1.IsBusy != true)
{
// Start the asynchronous operation.
backgroundWorker1.RunWorkerAsync();
}
}
private void cancelAsyncButton_Click(object sender, EventArgs e)
{
if (backgroundWorker1.WorkerSupportsCancellation == true)
{
// Cancel the asynchronous operation.
backgroundWorker1.CancelAsync();
}
}
// This event handler is where the time-consuming work is done.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 1; i <= 10; i++)
{
if (worker.CancellationPending == true)
{
e.Cancel = true;
break;
}
else
{
// Perform a time consuming operation and report progress.
System.Threading.Thread.Sleep(500);
worker.ReportProgress(i * 10);
}
}
}
// This event handler updates the progress.
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
resultLabel.Text = (e.ProgressPercentage.ToString() + "%");
}
// This event handler deals with the results of the background operation.
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled == true)
{
resultLabel.Text = "Canceled!";
}
else if (e.Error != null)
{
resultLabel.Text = "Error: " + e.Error.Message;
}
else
{
resultLabel.Text = "Done!";
}
}
}
}
下面的代碼示例演示如何使用 BackgroundWorker 類異步執行耗時的操做。 下圖顯示輸出的示例。
該操做計算選定的斐波納契數,在計算過程當中報告進度更新,並容許取消掛起的計算。
using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
namespace BackgroundWorkerExample
{
public class FibonacciForm : System.Windows.Forms.Form
{
private int numberToCompute = 0;
private int highestPercentageReached = 0;
private System.Windows.Forms.NumericUpDown numericUpDown1;
private System.Windows.Forms.Button startAsyncButton;
private System.Windows.Forms.Button cancelAsyncButton;
private System.Windows.Forms.ProgressBar progressBar1;
private System.Windows.Forms.Label resultLabel;
private System.ComponentModel.BackgroundWorker backgroundWorker1;
public FibonacciForm()
{
InitializeComponent();
InitializeBackgroundWorker();
}
// Set up the BackgroundWorker object by
// attaching event handlers.
private void InitializeBackgroundWorker()
{
backgroundWorker1.DoWork +=
new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(
backgroundWorker1_RunWorkerCompleted);
backgroundWorker1.ProgressChanged +=
new ProgressChangedEventHandler(
backgroundWorker1_ProgressChanged);
}
private void startAsyncButton_Click(System.Object sender,
System.EventArgs e)
{
// Reset the text in the result label.
resultLabel.Text = String.Empty;
// Disable the UpDown control until
// the asynchronous operation is done.
this.numericUpDown1.Enabled = false;
// Disable the Start button until
// the asynchronous operation is done.
this.startAsyncButton.Enabled = false;
// Enable the Cancel button while
// the asynchronous operation runs.
this.cancelAsyncButton.Enabled = true;
// Get the value from the UpDown control.
numberToCompute = (int)numericUpDown1.Value;
// Reset the variable for percentage tracking.
highestPercentageReached = 0;
// Start the asynchronous operation.
backgroundWorker1.RunWorkerAsync(numberToCompute);
}
private void cancelAsyncButton_Click(System.Object sender,
System.EventArgs e)
{
// Cancel the asynchronous operation.
this.backgroundWorker1.CancelAsync();
// Disable the Cancel button.
cancelAsyncButton.Enabled = false;
}
// This event handler is where the actual,
// potentially time-consuming work is done.
private void backgroundWorker1_DoWork(object sender,
DoWorkEventArgs e)
{
// Get the BackgroundWorker that raised this event.
BackgroundWorker worker = sender as BackgroundWorker;
// Assign the result of the computation
// to the Result property of the DoWorkEventArgs
// object. This is will be available to the
// RunWorkerCompleted eventhandler.
e.Result = ComputeFibonacci((int)e.Argument, worker, e);
}
// This event handler deals with the results of the
// background operation.
private void backgroundWorker1_RunWorkerCompleted(
object sender, RunWorkerCompletedEventArgs e)
{
// First, handle the case where an exception was thrown.
if (e.Error != null)
{
MessageBox.Show(e.Error.Message);
}
else if (e.Cancelled)
{
// Next, handle the case where the user canceled
// the operation.
// Note that due to a race condition in
// the DoWork event handler, the Cancelled
// flag may not have been set, even though
// CancelAsync was called.
resultLabel.Text = "Canceled";
}
else
{
// Finally, handle the case where the operation
// succeeded.
resultLabel.Text = e.Result.ToString();
}
// Enable the UpDown control.
this.numericUpDown1.Enabled = true;
// Enable the Start button.
startAsyncButton.Enabled = true;
// Disable the Cancel button.
cancelAsyncButton.Enabled = false;
}
// This event handler updates the progress bar.
private void backgroundWorker1_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
this.progressBar1.Value = e.ProgressPercentage;
}
// This is the method that does the actual work. For this
// example, it computes a Fibonacci number and
// reports progress as it does its work.
long ComputeFibonacci(int n, BackgroundWorker worker, DoWorkEventArgs e)
{
// The parameter n must be >= 0 and <= 91.
// Fib(n), with n > 91, overflows a long.
if ((n < 0) || (n > 91))
{
throw new ArgumentException(
"value must be >= 0 and <= 91", "n");
}
long result = 0;
// Abort the operation if the user has canceled.
// Note that a call to CancelAsync may have set
// CancellationPending to true just after the
// last invocation of this method exits, so this
// code will not have the opportunity to set the
// DoWorkEventArgs.Cancel flag to true. This means
// that RunWorkerCompletedEventArgs.Cancelled will
// not be set to true in your RunWorkerCompleted
// event handler. This is a race condition.
if (worker.CancellationPending)
{
e.Cancel = true;
}
else
{
if (n < 2)
{
result = 1;
}
else
{
result = ComputeFibonacci(n - 1, worker, e) +
ComputeFibonacci(n - 2, worker, e);
}
// Report progress as a percentage of the total task.
int percentComplete =
(int)((float)n / (float)numberToCompute * 100);
if (percentComplete > highestPercentageReached)
{
highestPercentageReached = percentComplete;
worker.ReportProgress(percentComplete);
}
}
return result;
}
#region Windows Form Designer generated code
private void InitializeComponent()
{
this.numericUpDown1 = new System.Windows.Forms.NumericUpDown();
this.startAsyncButton = new System.Windows.Forms.Button();
this.cancelAsyncButton = new System.Windows.Forms.Button();
this.resultLabel = new System.Windows.Forms.Label();
this.progressBar1 = new System.Windows.Forms.ProgressBar();
this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker();
((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).BeginInit();
this.SuspendLayout();
//
// numericUpDown1
//
this.numericUpDown1.Location = new System.Drawing.Point(16, 16);
this.numericUpDown1.Maximum = new System.Decimal(new int[] {
91,
0,
0,
0});
this.numericUpDown1.Minimum = new System.Decimal(new int[] {
1,
0,
0,
0});
this.numericUpDown1.Name = "numericUpDown1";
this.numericUpDown1.Size = new System.Drawing.Size(80, 20);
this.numericUpDown1.TabIndex = 0;
this.numericUpDown1.Value = new System.Decimal(new int[] {
1,
0,
0,
0});
//
// startAsyncButton
//
this.startAsyncButton.Location = new System.Drawing.Point(16, 72);
this.startAsyncButton.Name = "startAsyncButton";
this.startAsyncButton.Size = new System.Drawing.Size(120, 23);
this.startAsyncButton.TabIndex = 1;
this.startAsyncButton.Text = "Start Async";
this.startAsyncButton.Click += new System.EventHandler(this.startAsyncButton_Click);
//
// cancelAsyncButton
//
this.cancelAsyncButton.Enabled = false;
this.cancelAsyncButton.Location = new System.Drawing.Point(153, 72);
this.cancelAsyncButton.Name = "cancelAsyncButton";
this.cancelAsyncButton.Size = new System.Drawing.Size(119, 23);
this.cancelAsyncButton.TabIndex = 2;
this.cancelAsyncButton.Text = "Cancel Async";
this.cancelAsyncButton.Click += new System.EventHandler(this.cancelAsyncButton_Click);
//
// resultLabel
//
this.resultLabel.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
this.resultLabel.Location = new System.Drawing.Point(112, 16);
this.resultLabel.Name = "resultLabel";
this.resultLabel.Size = new System.Drawing.Size(160, 23);
this.resultLabel.TabIndex = 3;
this.resultLabel.Text = "(no result)";
this.resultLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// progressBar1
//
this.progressBar1.Location = new System.Drawing.Point(18, 48);
this.progressBar1.Name = "progressBar1";
this.progressBar1.Size = new System.Drawing.Size(256, 8);
this.progressBar1.Step = 2;
this.progressBar1.TabIndex = 4;
//
// backgroundWorker1
//
this.backgroundWorker1.WorkerReportsProgress = true;
this.backgroundWorker1.WorkerSupportsCancellation = true;
//
// FibonacciForm
//
this.ClientSize = new System.Drawing.Size(292, 118);
this.Controls.Add(this.progressBar1);
this.Controls.Add(this.resultLabel);
this.Controls.Add(this.cancelAsyncButton);
this.Controls.Add(this.startAsyncButton);
this.Controls.Add(this.numericUpDown1);
this.Name = "FibonacciForm";
this.Text = "Fibonacci Calculator";
((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).EndInit();
this.ResumeLayout(false);
}
#endregion
[STAThread]
static void Main()
{
Application.Run(new FibonacciForm());
}
}
}
.NET Framework
受如下版本支持:四、3.五、3.0、2.0
.NET Framework Client Profile
受如下版本支持:四、3.5 SP1
Windows 7, Windows Vista SP1 或更高版本, Windows XP SP3, Windows XP SP2 x64 Edition, Windows Server 2008(不支持服務器核心), Windows Server 2008 R2(支持 SP1 或更高版本的服務器核心), Windows Server 2003 SP2
.NET Framework 並非對每一個平臺的全部版本都提供支持。有關支持的版本的列表,請參見
.NET Framework 系統要求。
此類型的任何公共
static(在 Visual Basic 中爲 Shared) 成員都是線程安全的。但不保證全部實例成員都是線程安全的。
Date |
修訂記錄 |
緣由 |
2010 年 9 月 |
已添加簡單的 BackgroundWorker 代碼示例。 |
客戶反饋 |
c#的ThreadPool使用筆記(一)
http://www.cnblogs.com/sashow/archive/2007/02/08/644679.html
c#的ThreadPool使用筆記(二)
http://www.cnblogs.com/sashow/archive/2007/02/08/645016.html
C#多線程
http://blog.csdn.net/ILOVEMSDN/article/details/1735495
ThreadPool(線程池) in .Net
http://www.cnblogs.com/rickie/archive/2004/11/23/67275.html