C# async await 實現機制詳解

一、async/await 異步編程實現機制

1.1 核心概念

async/await 是 C# 5.0 引入的語法糖,它基于**狀態機(State Machine)**模式實現,將異步方法轉換為編譯器生成的狀態機類。

1.2 編譯器轉換過程

當編譯器遇到 async 方法時,會將其轉換為一個實現了 IAsyncStateMachine 接口的狀態機類。

// 原始代碼
public async Task<int> GetDataAsync()
{await Task.Delay(1000);return 42;
}

編譯器會將其轉換為類似以下結構:

// 偽代碼:編譯器生成的狀態機
[CompilerGenerated]
private sealed class <GetDataAsync>d__1 : IAsyncStateMachine
{public int <>1__state;public AsyncTaskMethodBuilder<int> <>t__builder;public YourClass <>4__this;private TaskAwaiter <>u__1;public void MoveNext(){int num = <>1__state;try{TaskAwaiter awaiter;if (num != 0){awaiter = Task.Delay(1000).GetAwaiter();if (!awaiter.IsCompleted){<>1__state = 0;<>u__1 = awaiter;<>t__builder.AwaitOnCompleted(ref awaiter, ref this);return;}}else{awaiter = <>u__1;<>u__1 = default(TaskAwaiter);<>1__state = -1;}awaiter.GetResult(); // 清理異常<>t__builder.SetResult(42); // 設置返回值}catch (Exception e){<>1__state = -2;<>t__builder.SetException(e);return;}}public void SetStateMachine(IAsyncStateMachine stateMachine){<>t__builder.SetStateMachine(stateMachine);}
}

1.3 關鍵組件解析

1.3.1 AsyncTaskMethodBuilder
  • 負責管理異步方法的生命周期
  • 包含 Task 的創建、狀態管理和結果設置
  • 提供 AwaitOnCompletedSetResultSetException 等方法
1.3.2 狀態機工作流程
  1. 初始狀態 (<>1__state = -1):方法開始執行
  2. 等待狀態 (<>1__state = 0):遇到 await 且任務未完成
  3. 完成狀態 (<>1__state = -2):方法執行完畢或發生異常
1.3.3 await 操作的執行過程
  1. 調用 GetAwaiter() 獲取 TaskAwaiter
  2. 檢查 IsCompleted 屬性
  3. 如果未完成:
    • 保存當前狀態
    • 注冊 continuation 回調
    • 返回控制權給調用者
  4. 如果已完成:繼續執行后續代碼

1.4 上下文捕獲(Context Capture)

await 默認會捕獲當前的 SynchronizationContextTaskScheduler

public async Task ProcessAsync()
{// 捕獲當前上下文await SomeAsyncOperation();// 回到原始上下文執行后續代碼UpdateUI(); // 在UI線程上執行
}

二、死鎖產生的原因

2.1 同步阻塞導致的死鎖

最常見的死鎖場景:在同步代碼中阻塞等待異步操作完成。

// 危險代碼 - 可能導致死鎖
public int GetData()
{// 死鎖!等待異步方法完成return GetDataAsync().Result;
}public async Task<int> GetDataAsync()
{await Task.Delay(1000);return 42;
}

死鎖形成過程:

  1. GetData() 調用 GetDataAsync()
  2. GetDataAsync() 開始執行,遇到 await
  3. 線程池線程被釋放,GetData() 在主線程阻塞等待
  4. await 完成后,需要回到原始上下文(主線程)繼續執行
  5. 但主線程被 Result 阻塞,無法執行 continuation
  6. 形成死鎖

2.2 UI線程死鎖

在WinForms/WPF應用中特別常見:

private async void Button_Click(object sender, EventArgs e)
{// 危險:在UI事件中同步等待var result = GetDataAsync().Result;textBox.Text = result.ToString();
}

2.3 ASP.NET 經典死鎖

在ASP.NET Framework中:

public ActionResult GetData()
{// 可能死鎖var data = GetDataAsync().Result;return Json(data);
}

三、死鎖解決方案

3.1 根本原則:避免同步阻塞

錯誤做法:

// ? 避免使用
var result = DoAsync().Result;
var result = DoAsync().Wait();
var result = DoAsync().GetAwaiter().GetResult();

正確做法:

// ? 使用 async/await 鏈式調用
public async Task<int> GetDataAsync()
{return await GetDataAsync();
}

3.2 解決方案一:異步編程鏈

將同步方法改為異步:

// 原始同步方法
public int GetData()
{return GetDataAsync().Result; // 死鎖風險
}// 改為異步方法
public async Task<int> GetDataAsync()
{return await GetDataAsync();
}// 調用者也需要異步
public async Task ProcessAsync()
{var data = await GetDataAsync();// 處理數據
}

3.3 解決方案二:ConfigureAwait(false)

在類庫中使用 ConfigureAwait(false) 避免上下文捕獲:

public async Task<int> GetDataAsync()
{// 不捕獲上下文,避免死鎖await Task.Delay(1000).ConfigureAwait(false);// 繼續異步操作await AnotherAsyncOperation().ConfigureAwait(false);return 42;
}

使用場景:

  • 類庫開發
  • 不需要訪問UI組件的后臺操作
  • ASP.NET Core 應用

3.4 解決方案三:創建新線程執行

當必須同步調用時,使用新線程:

public int GetData()
{// 在新線程中執行異步方法return Task.Run(async () => await GetDataAsync()).Result;
}

四、最佳實踐

4.1 類庫開發

// 類庫中始終使用 ConfigureAwait(false)
public async Task<ServiceResult> CallServiceAsync()
{var response = await httpClient.GetAsync(url).ConfigureAwait(false);var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);return JsonConvert.DeserializeObject<ServiceResult>(content);
}

4.2 UI應用開發

// UI事件處理保持異步
private async void Button_Click(object sender, EventArgs e)
{try{button.Enabled = false;var result = await GetDataAsync(); // 不使用 .ResulttextBox.Text = result.ToString();}catch (Exception ex){MessageBox.Show(ex.Message);}finally{button.Enabled = true;}
}

4.3 異步Main方法

// .NET 4.7.1+ 支持 async Main
static async Task<int> Main(string[] args)
{try{await ProcessAsync();return 0;}catch (Exception ex){Console.WriteLine(ex.Message);return 1;}
}

五、總結

  1. async/await 是基于狀態機的編譯器魔法
  2. 死鎖 主要由同步阻塞和上下文捕獲引起
  3. 最佳解決方案 是保持異步調用鏈
  4. 類庫開發 應使用 ConfigureAwait(false)
  5. 避免 在異步代碼中使用 .Result.Wait()

遵循這些原則,可以安全高效地使用C#的異步編程模型。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/917538.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/917538.shtml
英文地址,請注明出處:http://en.pswp.cn/news/917538.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Servlet 學習筆記

本文為記錄Servlet學習時的一些筆記和代碼 課程參考黑馬程序員 對于Java Web 學習的一個復習一 概述server applet 運行在服務器端的小程序 本質就是一個接口 定義java類被瀏覽器訪問到&#xff08;Tomcat識別&#xff09;的規則我們會自定義這樣一個類來實現復寫方法實現接口二…

【maven】倉庫配置

目錄 一、本地倉庫 二、私有倉庫 三、阿里云倉庫 一、本地倉庫 針對無外網、無maven私服&#xff0c;只有本地倉庫&#xff0c;進行maven項目開發。在maven的settings.xml中設置三項&#xff1a; 1、本地倉庫地址 默認在當前系統用戶下創建目錄&#xff1a;.m2/repository…

信息系統架構設計的系統性解析

一、信息系統架構設計??概念定義??&#xff1a;信息系統架構&#xff08;ISA&#xff09;是對系統組件、交互關系及環境約束的結構化抽象&#xff0c;確保業務目標與技術實現對齊。核心要素包括業務邏輯層、數據層、應用層和基礎設施層。??設計方法??&#xff1a;??T…

編譯舊版本的electron內核

我們的軟件產品使用的electron框架&#xff0c;electron框架是不斷更新的&#xff0c;但是我們的軟件太過龐大復雜&#xff0c;不敢輕易升級electron框架。Electron舊版本存在一些BUG和不合理的地方&#xff0c;需要去修復BUG或者不合理的地方。修復完BUG后&#xff0c;要對ele…

Elastic 9.1/8.19:默認啟用 BBQ,ES|QL 支持跨集群搜索(CCS)正式版,JOINS 正式版,集成 Azure AI Foundry

作者&#xff1a;來自 Elastic Mark Doncov 今天&#xff0c;我們很高興宣布 Elastic 9.1 和 8.19 正式發布&#xff01; 是的&#xff0c;又有好消息 —— 我們將 8.x 系列最終延長到 8.19&#xff0c;這樣那些還在等待升級到 9.x 的用戶也能享受到許多新功能。 Elastic 9.1…

Redis面試精講 Day 8:Stream消息隊列設計與實現

【Redis面試精講 Day 8】Stream消息隊列設計與實現 文章標簽 Redis,消息隊列,Stream,面試技巧,分布式系統,后端開發 文章簡述 本文是"Redis面試精講"系列第8天&#xff0c;聚焦Redis 5.0引入的Stream消息隊列。文章深入解析Stream的核心概念與實現原理&#xff0…

【01】大恒相機SDK C++開發 —— 初始化相機,采集第一幀圖像、回調采集、關閉相機

文章目錄1 初始化相機&#xff0c;采集第一幀圖像2 回調方式采集圖像3 視頻教程1 初始化相機&#xff0c;采集第一幀圖像 #include <iostream> #include <GalaxyIncludes.h> using namespace std;int main() {//首先&#xff0c;對相機資源進行初始化IGXFactory::…

Windows下定位Mingw編譯的Qt程序崩潰堆棧

一、dump和pdb是什么 在Windows系統下&#xff0c;當我們寫的程序跑在客戶的機器上&#xff0c;因為一個bug&#xff0c;導致程序崩潰&#xff0c;我們該如何定位并修復這個bug呢&#xff1f; 有人會說記錄日志&#xff0c;即便有日志&#xff0c;也是不好定位的&#xff0c;因…

.net依賴注入框架 Autofac和MEF的對比

Autofac 默認需要顯式注冊每個類型&#xff0c;這是它與MEF在模塊化設計上的主要區別。以下是具體對比說明&#xff1a;1. Autofac 的基本注冊方式 Autofac 必須通過代碼明確注冊每個需要注入的類型&#xff08;除非使用特殊掃描機制&#xff09;&#xff1a; var builder new…

Python 使用 asyncio 包處理并 發(使用asyncio包編寫服務器)

使用asyncio包編寫服務器 演示 TCP 服務器時通常使用回顯服務器。我們要構建更好玩一點的示 例服務器&#xff0c;用于查找 Unicode 字符&#xff0c;分別使用簡單的 TCP 協議和 HTTP 協議實現。這兩個服務器的作用是&#xff0c;讓客戶端使用 4.8 節討論過的 unicodedata 模塊…

Node.js (Express) + MySQL + Redis構建項目流程

以下是使用 Node.js (Express) MySQL Redis 構建完整項目的詳細流程&#xff0c;涵蓋環境搭建、架構設計、核心代碼實現和部署優化&#xff1a;一、項目初始化 1. 創建項目目錄 mkdir my-project cd my-project npm init -y2. 安裝基礎依賴 npm install express mysql2 redis…

Python3 中使用zipfile進行文件(夾)的壓縮、解壓縮

一、文件壓縮與解壓縮模塊 zipfile簡介 zipfile 是 Python 標準庫中用于處理 ZIP 壓縮文件的模塊&#xff0c;提供了創建、讀取、寫入、解壓 ZIP 文件的完整功能。它支持多種壓縮算法&#xff0c;無需安裝額外依賴&#xff0c;是處理 ZIP 格式的首選工具。 核心功能與常用類 zi…

在Java客戶端使用Redis

目錄 第一步&#xff1a;開放Redis外部連接配置 第二步&#xff1a;配置端口轉發 第三步&#xff1a;在IDEA中導入依賴 第四步&#xff1a;編寫代碼命令 連接環境&#xff1a;Java客戶端為本地IDEA&#xff0c;Redis服務器安裝在云服務器Ubuntu系統中。 第一步&#xff1a;開…

【MySQL】MySQL索引—B樹/B+樹

目錄 1. 數據庫索引 1.1 索引的概念 1.2 索引的特點 1.3 索引查詢對比普通的查詢 1.4 索引的操作 1.5 索引的原理 1.6 B樹 1.7 B樹 1.8 B樹的優點 1. 數據庫索引 1.1 索引的概念 數據庫的索引是一種特殊的數據結構&#xff0c;里面包含著數據表中所有記錄的引用&…

jQuery Mobile 面板詳解

jQuery Mobile 面板詳解 引言 隨著移動設備的普及,移動網頁開發變得越來越重要。jQuery Mobile 是一個基于 jQuery 的移動網頁開發框架,它提供了一套豐富的 UI 組件和主題,使得開發者可以快速構建出美觀、響應式的移動網頁。在 jQuery Mobile 中,面板(Panel)是一個非常…

Python中的import和from...import有什么區別?

文章目錄 前言 一、import導入模塊 導入模塊并給它一個別名 語法格式 二、from...import導入特定項 1.導入模塊中的特定項 2.導入模塊中的所有項 2.1 命名空間核污染 2.2 性能影響 總結 前言 在Python編程中,模塊和包的導入機制是編寫可維護、可擴展代碼的核心。深入理解Pyth…

vscode提示“無法使用 compilerPath 解析配置”解決辦法

0 問題描述 使用vscode的Remote-SSH插件連接安裝在虛擬機上的Windows10進行遠程開發時&#xff0c;出現如下提示&#xff1a;無法使用 compilerPath 解析配置:“D:\mingw64\bin\gcc.exe” 所有包含C庫頭文件的文件都被標紅提示錯誤&#xff1a;1 問題原因 vscode沒有設置正確的…

信噪比(Signal-to-Noise Ratio, SNR)詳細介紹

信噪比&#xff08;Signal-to-Noise Ratio, SNR&#xff09;信噪比&#xff08;Signal-to-Noise Ratio&#xff0c;SNR&#xff09;是衡量信號質量的重要參數&#xff0c;表示有用信號的功率與背景噪聲功率的比值。SNR在通信、音頻處理、視頻處理以及其他電子信號處理領域中具有…

Nginx 相關實驗(1)

nginx源碼編譯 本實驗采用nginx源碼編譯的安裝方式&#xff0c;需要準備一個tar包&#xff0c;可從nginx官網上下載。 下載地址&#xff1a;nginx: downloadhttps://nginx.org/en/download.html 將下載好的壓縮包傳到虛擬機中的自定義目錄下 [rootwebserver ~]# ls anacond…

【選型】HK32L088 與 STM32F0/L0 系列 MCU 參數對比與選型建議(ST 原廠 vs 國產芯片)(單片機選型主要考慮的參數與因素)

國產 vs ST 單片機在工業控制中的性能對比分析 HK32L088 與 STM32F0/L0 系列 MCU 參數對比與選型建議 工業控制領域 MCU 選型:國產航順 HK32 與 ST 原廠芯片深入比較 國產 MCU 是否可替代 ST?基于發電機控制應用的深入評估 從數據手冊看 MCU 制造工藝差異:HK32L088 vs S…