async-await 線程分析

来源:https://www.cnblogs.com/ms27946/archive/2020/01/15/there-is-no-thread.html
-Advertisement-
Play Games

這裡沒有線程 原文地址: "https://blog.stephencleary.com/2013/11/there is no thread.html" 前言 我是在看 C 8.0 新特性非同步流時在評論里看到這篇文章的,閱讀之後發現這篇文章乾貨滿滿,作者解釋的非常清晰,裡面的本質分析內容在《CLR ...


這裡沒有線程

原文地址:https://blog.stephencleary.com/2013/11/there-is-no-thread.html

前言

我是在看 C#8.0 新特性非同步流時在評論里看到這篇文章的,閱讀之後發現這篇文章乾貨滿滿,作者解釋的非常清晰,裡面的本質分析內容在《CLR via C#》一書中也有講到。更加加深了我的印象。遂在這裡翻譯過來,以便加深自己的理解

正文

一個本質的事實就是純粹的非同步是不會產生線程的

反對這個事實的人有很多。“不”,他們喊道:“如果我正在等待一個操作,那麼這個線程就必須在執行等待!它可能是線程池線程。或者是一個操作系統(OS)線程,又或是其他設備驅動程式...”

無需理會他們。如果一個非同步操作是純粹的(pure),那麼就不會有線程的。

那些持懷疑態度的人。那我們就迎合他們罷。

我們將跟蹤一個非同步操作一直到硬體,特別是 .net 部分和硬體部分。我們必須通過省略一些中間的細節來簡化描述,但是我們不能偏離事實真相。

通常寫一個非同步操作(文件,網路流,USB 介面等等)。代碼如下:

private async void Button_Click(object sender, RoutedEventArgs s)
{
    byte[] data = ...
    await myDevice.WriteAsync(data, 0, data.Length);
}

我們已經知道在 await 的時候 UI 線程是不會阻塞的。那麼問題來了:這裡有沒有是其他線程在阻塞期間犧牲自己以至於讓 UI 線程存活呢?

那讓我們繼續往下深究

第一步:庫(比如進入到 BCL 庫源代碼)。我們假使 WriteAsync 是通過 http://msdn.microsoft.com/en-us/library/system.threading.overlapped.aspx 來實現的,它是基於overllapped I/O 的。所有它在一個設備驅動程式的句柄上開始一個 Win32 overlapped 操作。

OVERLAPPED 是一個包含了用於非同步輸入輸出的信息的結構體;詳細解釋移步 https://en.wikipedia.org/wiki/Overlapped_I/O

操作系統然後就會轉向設備驅動程式並開始請求一個寫操作。它首先會構造一個表示寫請求的對象;它被稱為 I/O Request Packet(IRP)。

設備驅動程式接受到 IRP 之後並向設備提交一個寫出數據的命令。如果這個設備支持直接記憶體訪問(DMA 全稱是 Direct Memory Access),這能夠像寫到緩衝區到寄存器一樣簡單。這就是設備驅動程式所做的一切;它把 IRP 標記為 "pending" (掛起) 並返回給操作系統。

本質核心就在這裡:設備驅動程式正在處理 IRP 時是不會允許阻塞的。也就是說如果 IRP 不能馬上完成,那麼它必須要非同步處理。甚至是同步 API 也是如此!在設備驅動程式級別,所有的請求(重要的)都是非同步的。

這裡引用了 Tomes知識,“無論I/O請求的類型如何,在內部,代表應用程式向驅動程式發出的I/O操作都是非同步執行的”

在 IRP 掛起的時候,OS 返回庫,庫返回了一個未完成的任務給按鈕點擊事件,並暫停了 async 方法, UI 線程繼續執行。

我們跟著請求繼續往下走,現在到達了設備的物理層

現在寫操作正在進行。那麼有多少線程正處理它呢?

沒有。

這裡沒有設備驅動程式線程、OS 線程、庫(BCL)線程或者是線程池線程操作寫操作。這裡沒有線程

現在我們來跟著從來自內核的相應回到最初的世界。

在開始寫請求之後的一段時間,設備完成了寫操作。它會以中斷的方式來通知 CPU。

設備驅動程式的中斷服務程式(ISR(Interrupt Service Routine) )響應中斷。這個中斷是 CPU 級別的事件,無論哪個線程正在運行都會臨時的搶占 CPU 的控制權。你可以認為 ISR 是在“借”當前正在運行的線程,但是我更傾向於 ISR 運行時的級別非常低,以至於不存在“線程”的概念。可以這麼說,它們在所有線程之下進來的。

不管怎樣,ISR 正確寫完了,完了它會通知設備程式 “謝謝你的中斷” 並且進入 DPC(Deffered Procedure Call) 隊列(延遲過程調用)

當 CPU 被中斷干擾時,它將會到達 DPCs。DPCs 也會執行在一個很低的級別以至於說它是一個線程是不正確的;就像 ISRs,DPCs 直接在 CPU 上運行,線上程系統之下。

PDC 接受代表寫請求的 IRP 並且標記為 “已完成”。然而,這個“完成”狀態只存在於 OS 級別;進程有它自己的記憶體空間,它必須被通知。所以 OS 會入隊列一個特殊內核模式非同步過程調用(APC)到擁有自己句柄的線程。

由於 BCL 庫使用了標準的 P/Invoke overlapped I/O 系統,它已經在 I/O Completion Port(IOCP)註冊句柄,它是線程池的一部分。所以借用 I/O 線程池線程來執行 APC,它會通知這些任務已經完成了。

這個任務已經捕捉了 UI 上下文,所以它不會直接線上程池線程上恢復非同步方法。而是它將該方法的延續排隊到 UI 上下文中,並且 UI 線程將恢復執行那個方法。

所以我們看到,正當一個請求處理時這裡是沒有線程的。當請求完成時,一些線程被借過去或者是被短暫的排隊。這項工作通常在 1 毫秒左右(例如 APC 運行線上程池線程)或 1 微妙左右(例如 ISR)。但是這裡沒有線程是阻塞的,僅僅只是等待請求完成。

現在,我們遵循的路徑是標準路徑,這是如此清晰簡單。這裡有無數的變數,但是核心是不變的。

所以說 “這裡必須有一個線程是在處理非同步操作” 是不正確的。

釋懷吧,不要嘗試找到非同步線程——這是不可能的。而是你應該去瞭解真相:

沒有線程

該篇文章的評論也是很精彩的,特別是討論將非同步操作當作消息處理那部分討論,建議也花時間看下

本文同步至:https://github.com/MarsonShine/MarsonShine.github.io/blob/master/mardown/async/There-IS-NO-Thread.md


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 歸併排序利用分治策略進行排序。原理如下 分解:分解待排的n個元素的序列成個具n/2個元素的兩個子序列。 解決:使用歸併排序遞歸地排序兩個子序列。 合併:合併兩個已排序的子序列以產生已排序的答案。 歸併排序的時間複雜度是θ(nlgn)。 歸併排序是穩定排序之一。 歸併排序不是原址排序,在合併階段需要申 ...
  • 插入排序是最常用的排序之一。 在輸入規模較小的時候,插入排序的性能較好。 最好情況下插入排序的時間複雜度是O(n),平均情況則為O(n2)。 插入排序是穩定的排序演算法之一。 基本思路為從第二個元素開始,依次插入前面已經排好序的序列,利用迴圈不變式很容易理解。 代碼如下:(僅供參考) 1 void I ...
  • 1. MVC設計模式 MVC設計模式:Model-View-Controller簡寫。 最早由TrygveReenskaug在1978年提出,是施樂帕羅奧多研究中心(Xerox PARC)在20世紀80年代為程式語言Smalltalk發明的一種軟體設計模式,是為了將傳統的輸入(input)、處理(p ...
  • CPU的組成 CPU是由運算器(信息處理)、控制器(控制器件工作)、寄存器(信息存儲)等器件組成,他們之間通過匯流排相連。 通用寄存器 通用寄存器時用於存放一般性數據的,以8086 CPU為例,8086 CPU所有的寄存器都是16位的,8086 CPU中的通用寄存器有AX、BX、CX、DX。為了相容上 ...
  • 1.使用“\d+”匹配全數字 代碼: 1 import re 2 3 zen = "Arizona 479, 501, 870. Carlifornia 209, 213, 650." 4 5 m = re.findall("\d+", zen) 6 7 print(m) 結果: ['479', ' ...
  • hibernate中的延遲載入(lazyload)分屬性的延遲載入和關係的延遲載入 屬性的延遲載入: 當使用load的方式來獲取對象的時候,只有訪問了這個對象的屬性,hibernate才會到資料庫中進行查詢。否則不會訪問資料庫 Load的載入方式:1、Load採用延遲載入的方式,hibernate的 ...
  • Web服務 Web服務可以讓你在HTTP協議的基礎上通過XML或者JSON來交換信息。如果你想知道上海的天氣預報、中國石油的股價或者淘寶商家的一個商品信息,你可以編寫一段簡短的代碼,通過抓取這些信息然後通過標準的介面開放出來,就如同你調用一個本地函數並返回一個值。 Web服務背後的關鍵在於平臺的無關 ...
  • AbstractCollection介紹 AbstractCollection抽象類是Collection的基本實現,其實現了Collection中的大部分方法,可以通過繼承此抽象類以最少的代價來自定義Collection; 如果要定義一個不可變Collection,只需要繼承此類,並實現itera ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...