1. 程式人生 > >C#多線程之旅(3)

C#多線程之旅(3)

它的 logs 更新 nth 使用 targe result 右下角 介紹

閱讀目錄

  • 代碼下載
  • 一、介紹
  • 二、通過TPL進入線程池
  • 三、不用TPL進入到線程池
v博客前言

先交代下背景,寫《C#多線程之旅》這個系列文章主要是因為以下幾個原因:1.多線程在C/S和B/S架構中用得是非常多的;2.而且多線程的使用是非常復雜的,如果沒有用好,容易造成很多問題。

技術分享 v寫在前面

多線程,有利也有弊,使用需謹慎。

v正文開始

原文地址:C#多線程之旅(3)——線程池

C#多線程之旅(1)——介紹和基本概念

C#多線程之旅(2)——創建和開始線程

C#多線程之旅(3)——線程池

C#多線程之旅(4)——APM初探

C#多線程之旅(5)——同步機制介紹

C#多線程之旅(6)——詳解多線程中的鎖

更多文章正在更新中,敬請期待......

C#多線程之旅(3)——線程池

回到頂部

代碼下載

Thread_博客園_cnblogs_jackson0714.zip

第一篇~第三篇的代碼示例:

技術分享

源碼地址:https://github.com/Jackson0714/Threads

回到頂部

一、介紹

無論你什麽時候開始一個線程,幾百毫秒會花在整理一個新的local variable stack。每一個線程默認會消耗1MB的內存。線程池通過分享和回收線程來削減這些開銷,允許多線程被應用在一個非常顆粒級的級別而沒有性能損失。當充分利用多核系統去執行密集型計算的並行代碼時這是非常有用的。

線程池也會在線程的總數量上保持一個限制,從而使線程能夠更平穩地運行。太多的線程將會造成管理負擔和使CPU緩存是小,從而造成操作系統不能運行。一旦一個限制到達,job排隊等待直到另外一個完成才開始。這會使任意的並行應用程序成為可能,比如一個web server(同步方法是高級技巧,可以更高效地使用線程池中的線程)。

下面是幾種方式進入線程池:

  1. 通過Task Parallel Library.NET 4.0
  2. 通過調用ThreadPool.QueueUserWorkItem
  3. 通過asynchronous delegates
  4. 通過BackgroundWorkder

下面的結構直接使用線程池:

  1. WCF,Remoting,ASP.NET,ASMX Web Services application servers
  2. System.Timers.Timer and System.Threading.Timer
  3. Framework methods Async結束,比如WebClient(the event-based asynchronous pattern)和大部分的BeginXXX方法(the asynchronous programming model pattern)
  4. PLINQ

Task Parallel Library(TPL)PLINQ是充分有效的和高等級的,甚至當線程池是不重要的時候,你也會想使用它們去協助處理多線程。

現在我們簡單的看一下我們怎樣使用Task類來實現一個簡單的運行在線程池上的委托。

當使用線程池時需要註意下面的事情:

  1. 你不能設置一個線程的名字,因為設置線程的名字將會使調試更困難(當你在VS線程窗口中調試時,即使你可以附加一個描述)。
  2. 線程池中的線程總是後臺線程(這通常不是問題)。
  3. 在應用程序的開始期間,阻塞一個線程可能會觸發一個延遲,除非你調用ThreadPool.SetMinThreads

你不能任意地改變池中的線程的優先級-因為當它釋放會池中的時候,優先級會被還原為正常狀態。

你可以通過屬性Thread.CurrentThread.IsThreadPoolThread的屬性查詢線程是否是正在運行的一個池中的線程

回到頂部

二、通過TPL進入線程池

你可以使用在TaskParallel Library中的Task類來輕松的進入線程池。這個Task類在Framework 4.0中有介紹:如果你對老的結構比較熟悉,考慮用非泛型的Task類替換ThreadPool.QueueUserWorkItem,將Asunchoronous delgates替換為泛型Task<TResult>。最新的結構速度更快,更方便,而且更復雜。

為了使用非泛型的任務類,調用Task.Factory.StartNew方法,將方法傳進委托中。

Task.Factory.StartNew會返回一個Task對象,你可以使用它去監控這個task,比如,你可以調用它的wait方法等待它直到它完成。

static void Main(string[] args)
{
	Task task =  Task.Factory.StartNew(Go);
	task.Wait();
	Console.ReadKey();
}

static void Go()
{
	Console.WriteLine("From the thread pool start...");
	Thread.Sleep(3000);
	Console.WriteLine("From the thread pool end");
}

當你調用taskWait 方法時,一個未處理的異常會很容易地重新拋出到宿主線程上。(如果你不調用Wait方法而是放棄這個task,一個未處理的異常將會關閉掉這個進程)

泛型Task<Tresult>類是非泛型Task的子類。它讓你從這個已經完成執行的task中得到一個返回值。在下面的例子中,我們使用Task<TResult>來下載一個web page

static void Main(string[] args)
{
	Task<string> task = Task.Factory.StartNew<string>(
		() => DownloadString("http://www.baidu.com"));
	//調用其他方法
	//

	//可以用task的Result的屬性來獲得task返回值。
	//如果這個任務還在運行,當前的主線程將會被阻塞,直到這個任務完成。
	string result = task.Result;
}

static string DownloadString(string uri)
{ 
	using(var wc = new System.Net.WebClient())
	{
		return wc.DownloadString(uri);
	}
}

Task Parallel Library有許多的功能,特別是提升多核處理器的性能。我們會在並行編程中繼續討論TPL

回到頂部

三、不用TPL進入到線程池

如果你的應用程序是.NET Framework的早期版本(4.0之前的版本),你將不能使用TPL。你必須使用老的結構進入線程池:

ThreadPool.QueueUserWorkItemasynchoronous delegates.兩者的不同點是asynchronous delegates讓你從線程那裏返回數據。Asynchronous delegates收集任何exception返回給調用者。

要使用QueueUserWorkItem,只需調用這個方法的運行在線程池上的委托。

static void Main(string[] args)
{
	ThreadPool.QueueUserWorkItem(Go);
	ThreadPool.QueueUserWorkItem(Go, 123);
	Console.ReadKey();
}

static void Go(object data)
{
	Console.WriteLine("A from thread pool! " + data);
}

技術分享

我們的目標方法Go,必須接收一個簡單object類型的參數(為了滿足waitCallBack委托)。這將提供一個簡單的方式傳遞數據到方法中,就像是ParameterizedThreadStart。不像TaskQueueUserWorkItem不會返回一個對象去幫助你之後管理執行。還有,你必須顯式在目標方法的代碼中寫處理異常的代碼-因為未處理的異常將會終止程序。

ThreadPool.QueueUserWorkItem沒有提供從一個已經完成的線程中得到它的返回值的機制。Asynchronous delegate invocations(asynchronous delegates for short)解決了這個問題,允許任何個數類型化的參數在兩個方向傳遞。此外,在asynchronous delegates上未處理的異常很方便地在原始線程上重新拋出(更準確地說,這個線程叫做EndInvoke,因此不需要顯示處理。

不要混淆asynchronous delegates和asynchronous method(方法以Begin和End開頭的,比如File.BeginRead/File.EndRead)。Asynchronous methods表面上按照簡單的協議,但是它們的存在是為了解決一個更困難的問題。

下面是怎樣通過一個asynchronous delegate開始一個worker task:

  1. 實例化一個委托,該委托針對你想要並行運行的method(典型的是預定義Func delegates其中的一種)。
  2. delegate上調用BeginInvoke,保存它的IAsyncResult返回值。BeginInvoke立即返回給調用者。當其他池中的線程正在運行的時候,你可以執行其他動作。
  3. 當你需要這個結果,在delegate上調用EndInvoke,傳遞已保存的IAsyncResult對象。

在下面的例子中,我們使用一個asynchronous delegate invocation運行一個與主線程同時運行的簡單方法,這個方法返回一個字符串的長度:

static void Main(string[] args)
{
	Func<string, int> t = Go;
	IAsyncResult result = t.BeginInvoke("test", null, null);
	//
	// ... 這裏可以執行其他並行的任務
	//

	int length = t.EndInvoke(result);
	Console.WriteLine("String lenth is: " + length);
	Console.ReadKey();
}

static int Go(string messsage)
{
	return messsage.Length;
}

EndInvoke做三件事情。第一,如果asynchronous delegate沒有完成執行,則一直等待它完成。第二,接收返回值(以及任何ref或者out參數)。第三,返回任何未處理的線程異常給調用它的線程。

註意:如果你用asynchronous delegate調用的方法沒有返回值,你在技術上需要調用EndInvoke。在實踐中,這是開放的辯論;沒有Endinvoke報警去管理處罰未編譯者!如果你選擇不去調用EndInvoke,然而,你需要考慮在線程的異常去避免靜默失敗。

當你調用BeginInvoke方法時,可以指定一個call back delegate-一個可以接收一個IAsyncResult 對象的方法,它會在委托方法完成後被自動調用這個允許正在發動的線程忘記asynchronous delegate,但它在call back結束時需要一點額外的工作。

v寫在最後

線程池的使用的提升還沒有寫,最近兩年作息不規律,程序員得養好身體,早睡早起,睡覺睡覺。希望這篇博客能幫到大家,希望得到園友們的支持!


作  者: Jackson0714
出  處:http://www.cnblogs.com/jackson0714/
關於作者:專註於微軟平臺的項目開發。如有問題或建議,請多多賜教!
版權聲明:本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接。
特此聲明:所有評論和私信都會在第一時間回復。也歡迎園子的大大們指正錯誤,共同進步。或者直接私信我
聲援博主:如果您覺得文章對您有幫助,可以點擊文章右下角【推薦】一下。您的鼓勵是作者堅持原創和持續寫作的最大動力!

C#多線程之旅(3)