承接上文。web
線程池主要有兩個好處:編程
總之,Windows系統自帶了線程池的功能,一般狀況下,你不可能有更好的實現。因此只需瞭解如何使用。安全
Windows的線程池有兩種,分別是非託管線程池和託管線程池(即.NET線程池)。下面分別來介紹。多線程
其中的線程分爲IO線程和工做者線程(或非IO線程)。架構
QueueUserWorkItem
會讓一個監聽在IOCP上的工做者線程醒來,並執行例程。BindIOCompletionCallback
把一個文件句柄綁定到線程池的IOCP上。當此文件有關的IO操做完成時,一個工做者線程會被喚醒來執行後面的操做。QueueUserWorkItem
並傳入WT_EXECUTEINPERSISTENTTHREAD
標識時,會將一個APC回調放入IO線程的APC隊列中。其中的線程分爲工做者線程和IO線程。app
Task.Run()
或ThreadPool.QueueUserWorkItem()
來添加任務到任務隊列中。ThreadPool.UnsafeQueueNativeOverlapped()
能夠將任務添加到IO線程中。但不多使用。可見,託管與非託管線程池的差距是巨大的。異步
Thread
, ThreadPool
等接口。直接操做線程。Task
。async/await
。下面來着重分析Task編程模式。async
Task
最重要的兩個方法是Task.Run()
和new Task().Start()
。使用這兩個方法:異步編程
Task.Result
中。Task.Wait()
或Task.Result
會阻塞地等待工做線程執行結束。如下圖示演示了這個過程具體經歷了什麼。函數
調用Task.ContinueWith(continueAction, continueOptions)
。其中,ContinuationOptions是一個枚舉,提供了更多選項。
這個過程如何理解呢?請看下圖。
如下是一些主要的屬性:
它其實是Task.ContinueWith()
的語法糖。
爲此咱們來看一段程序:
private async void btnDoStuff_Click() { lblStatus.Content = "Doing Stuff"; await Task.Delay(4000); lblStatus.Content = "After await"; }
它至關於
private void btnDoStuff_Click() { lblStatus.Content = "Doing Stuff"; Task t = Task.Delay(4000); t.ContinueWith(task => lblStatus.Content = "After await"); }
可是ContinueWith
裏面的是一個委託,最好寫成函數。另寫一個函數最簡單,不過C#使用另外一種「狀態機」技術把這個函數寫在了原來的方法內。也就是說,第一段代碼實際上會被編譯爲(並不精確):
private void btnDoStuff_Click(int step) { switch (step) { case 0: lblStatus.Content = "Doing Stuff"; Task t = Task.Delay(4000); t.ContinueWith(task => btnDoStuff_Click(task.Step)); break; case 1: lblStatus.Content = "After await"; break; } }
方法被添加了一個參數step
, 第一次調用方法時step
爲0,此後每進入一次則加一。由此則用一個方法實現了兩端邏輯,這即是狀態機重入技術。C#的另外一個語法糖:用yield
實現IEnumerable
接口也是採用這種技術。
它在多線程的比如空氣:你能夠不知道它,但它很是重要。ExecutionContext
是爲了解決線程本地存儲在多線程中沒法傳遞的問題:總得有一種機制可以傳遞全局信息。不然只能經過函數調用參數傳遞了。
當一個線程發起異步調用的時候,ExecutionContext
會自動的在線程之間傳遞如下信息:
它是爲了描述異步調用返回時的行爲所建立的抽象。它有兩個基本接口方法:
Send
同步地等待任務執行完畢。Post
把任務發出去就無論了。那麼異步調用返回時的行爲是什麼意思?既然是抽象,那就會有具體的實現。後面咱們會看到幾種實現。
當開始異步調用時,C#會捕獲(capture)當前線程的同步上下文,並保存到Task中。在異步調用返回時,須要恢復(resume)同步上下文。此時就會調用同步上下文的Send
或者Post
。
下面是幾種典型的同步上下文實現:
ASP.NET同步上下文。它有如下特色:
最後,調用ConfigureAwait(false)
時,就會跳過恢復同步上下文這一過程。因此,有時候必要(當不必傳遞任何信息時,使用它能夠提升效率),有時候又會出錯。例如,UI程序的異步調用原本沒問題,你加了這個語句,反而會形成修改界面的操做可能不在UI線程中執行,從而出錯。可是注意,不管如何,執行上下文都是會傳遞的。
結合以上,第一段程序的更精確的編譯後版本是這樣的:
private void btnDoStuff_Click(int step) { switch (step) { case 0: lblStatus.Content = "Doing Stuff"; Task t = Task.Delay(4000); t.ContinueWith( task => SynchronizationContext.Current.Post( state => btnDoStuff_Click(task.Step), task) ); break; case 1: lblStatus.Content = "After await"; break; } }
這個問題曾經困擾我好久。若是我當前的線程調用一個異步調用後返回了,那究竟是誰在完成真正調用的工做呢?答案是一個(或幾個)共享的線程:線程池中的IO線程。
以下是一段代碼:
async void GetButton_OnClick(object o, EventArgs e) { Task<Image> task = GetFaviconAsync(_url); Image image = await task; AddAFavicon(image); } async Task<Image> GetFaviconAsync(string url) { var task = _webClient.DownloadDataTaskAsync(url); byte[] bytes = await task; return MakeImage(bytes); }
線程的執行狀況以下圖:
大部分的時間都在用戶線程中。只有調用到很是底層,IO完成以後,纔有IO線程被喚醒(見11),而後它調用Task的同步上下文的Post
,將剩下的任務再交給用戶線程去執行。
下面是一個動態的解釋:
關於IIS的架構和工做過程,有一些資料,這裏也不打算深究。提供兩張圖,以供理解。