C# 里非同步方法該如何理解?

C#里很多支持非同步方法調用的函數;比如TCPClient(對socket的封裝)的BeginReceive,BeginWrite等;

但不明白的是BeginReceive調用後,系統是如何調配線程來執行這個函數的;

一般接受數據的寫法是:

clientSocket.BeginReceive(receiveDataBuffer, bytesReceived, MESSAGE_LENGTH_SIZE - bytesReceived,SocketFlags.None,newAsyncCallback(RecieveComplete), clientSocket);
而這個回調里會繼續調用BeginReceive,以持續不斷地接收從伺服器發來的數據。

我想問的是這樣每次都非同步投遞,線程的切換不是很頻繁,會不會開銷大?

對C#線程機制不熟悉,請指教。謝謝。


關於背後的線程機制可以看這裡:《正確使用非同步操作》

這就是處理IO-Bound Operation的方式,很顯然,這也是一個非同步操作。當我們希望進行一個非同步的IO-Bound Operation時,CLR會(通過Windows API)發出一個IRP(I/O Request Packet)。當設備準備妥當,就會找出一個它「最想處理」的IRP(例如一個讀取離當前磁頭最近的數據的請求)並進行處理,處理完畢後設備將會(通過Windows)交還一個表示工作完成的IRP。CLR會為每個進程創建一個IOCP(I/O Completion Port)並和Windows操作系統一起維護。IOCP中一旦被放入表示完成的IRP之後(通過內部的ThreadPool.BindHandle完成),CLR就會儘快分配一個可用的線程用於繼續接下去的任務。


這種做法的需要一個重要條件,這就是發出用於請求的IRP的操作能夠立即返回,並且這個IO操作不會使用任何線程。而此時,這種非同步調用是真正地在節省資源,因為我們可以騰出線程用來處理其他任務了,這就是和第一種非同步調用的最大區別。不過很可惜,這種做法顯然需要操作系統和設備的支持,也就是只有特定的操作才能享受這些待遇。那麼.NET Framework中哪些操作能從中獲利呢?

  • FileStream操作:BeginRead、BeginWrite。調用BeginRead/BeginWrite時會發起一個非同步操作,但是只有在創建FileStream時傳入FileOptions.Asynchronous參數才能獲取真正的IOCP支持,否則BeginXXX方法將會使用默認定義在Stream基類上的實現。Stream基類中BeginXXX方法會使用委託的BeginInvoke方法來發起非同步調用——這會使用一個額外的線程來執行任務。雖然當前調用線程立即返回了,但是數據的讀取或寫入操作依舊佔用著另一個線程(IOCP支持的非同步操作時不需要線程的),因此並沒有任何「節省」,反而還很有可能降低了應用程序的性能,因為額外的線程切換會造成性能損失。
  • DNS操作:BeginGetHostByName、BeginResolve。
  • Socket操作:BeginAccept、BeginConnect、BeginReceive等等。
  • WebRequest操作:BeginGetRequestStream、BeginGetResponse。
  • SqlCommand操作:BeginExecuteReader、BeginExecuteNonQuery等等。這可能是開發一個Web應用時最常用的非同步操作了。如果需要在執行資料庫操作時得到IOCP支持,那麼需要在連接字元串中標記Asynchronous Processing為true(默認為false),否則在調用BeginXXX操作時就會拋出異常。
  • WebServcie調用操作:例如.NET 2.0或WCF生成的Web Service Proxy中的BeginXXX方法、WCF中ClientBase&的InvokeAsync方法。

所以沒錯,每次回調函數都是在IO ThreadPool里執行的,肯定會涉及到線程切換的開銷。至於大不大什麼的,太模糊,沒法回答。


@趙劼 好像對我說過socket的BeginReceive用的是I/O Completion Ports (Windows)


作為長期奮戰在內核的人員我補充兩句。

現代系統環境中線程切換的開銷一般是不必要做過多考慮的。在典型的當代x86(64)機器上,一次線程切換一般只會造成數百時鐘周期的開銷。相對於普通的I/O操作延遲比,這個時間太短了。如果對性能沒有極端要求,你根本不需要去想同步非同步在線程切換上的差距。

另外,關於肖進說的同步模式時大量線程佔用堆棧內存的情況,其實並不嚴重,現代操作系統都是on demand paging的,沒有touch到的棧空間不佔內存的。就我的個人經驗而言,在極限情況下同步I/O的性能反而好於非同步I/O,這有部分原因可能在於一次同步I/O對內核而言執行起來更加簡單的緣故。


Performing I/O-Bound Asynchronous Operations by Jeffrey Richter


推薦閱讀:

一個簡單的C#控制台小程序如下,可是不輸出,為什麼?
如何判斷 string 是否為合法的 C# 變數名?
怎麼看待 「C#已經沒落」 這種說法?
哪裡有比較全比較好的 C# 學習資料下載?
上層應用開發是否沒有底層開發有前途?

TAG:C# | Socket | 多線程 |