☕ NEW! 完成新手任務即可參加抽獎!LINE 星巴克禮券等你拿,名額有限!        🎉 推廣活動:邀請好友註冊 DevLearn,累積推薦抽 LINE 星巴克禮券! 活動詳情 →        🔥 活動期間 2026/4/1 - 5/31 |已有 0 人參加       
C# 基礎 中級

⚡ Async / Await 非同步程式設計

📌 為什麼需要非同步?

想像你是一個餐廳服務生

同步(Synchronous): 你幫客人 A 點餐 → 站在廚房門口等菜做好 → 上菜 → 然後才去服務客人 B。 (效率很差!等菜的時間完全浪費了)

非同步(Asynchronous): 你幫客人 A 點餐 → 把單子送進廚房 → 不等了,先去服務客人 B → 廚房做好了再回來上菜。 (效率高!等待的時間可以做別的事)

// 同步版本 — 一個一個等
void SyncExample()  // 同步方法
{
    Console.WriteLine("開始下載檔案 A...");
    Thread.Sleep(3000);  // 模擬等待 3 秒(什麼事都不能做!)
    Console.WriteLine("檔案 A 下載完成");

    Console.WriteLine("開始下載檔案 B...");
    Thread.Sleep(3000);  // 再等 3 秒
    Console.WriteLine("檔案 B 下載完成");
    // 總共花了 6 秒
}

// 非同步版本 — 同時進行
async Task AsyncExample()  // async 標記這是非同步方法
{
    Console.WriteLine("開始下載檔案 A...");
    Task taskA = Task.Delay(3000);  // 開始等待但不阻塞

    Console.WriteLine("開始下載檔案 B...");
    Task taskB = Task.Delay(3000);  // 同時開始等待

    await Task.WhenAll(taskA, taskB);  // 等兩個都完成
    Console.WriteLine("兩個檔案都下載完成!");
    // 總共只花了 3 秒(同時進行!)
}

📌 async / await 關鍵字

// async 放在方法宣告前面,表示這是一個非同步方法
// await 放在非同步操作前面,表示「等這個做完再繼續」

// 非同步方法的回傳型別
async Task DoSomethingAsync()           // 不回傳值用 Task
{
    await Task.Delay(1000);             // 等待 1 秒(非阻塞)
    Console.WriteLine("做完了!");      // 1 秒後執行
}

async Task<int> GetNumberAsync()         // 回傳 int 用 Task<int>
{
    await Task.Delay(500);               // 等待 0.5 秒
    return 42;                           // 回傳結果
}

async Task<string> GetGreetingAsync(string name) // 回傳 string 用 Task<string>
{
    await Task.Delay(100);               // 模擬非同步操作
    return $"你好,{name}!";           // 回傳問候語
}

// 呼叫非同步方法
async Task Main()  // Main 方法也可以是 async
{
    await DoSomethingAsync();            // 等待完成
    int number = await GetNumberAsync(); // 等待並取得結果
    Console.WriteLine(number);           // 輸出:42

    string greeting = await GetGreetingAsync("小明"); // 等待並取得結果
    Console.WriteLine(greeting);         // 輸出:你好,小明!
}

📌 Task 與 Task

// Task 代表一個「正在進行的工作」
// 就像你在餐廳點了餐,拿到一張號碼牌(Task)
// 號碼牌本身不是食物,但你可以用它來等食物做好

// 建立 Task 的方式
Task task1 = Task.Run(() =>              // 用 Task.Run 在背景執行
{
    Console.WriteLine("背景工作開始");   // 在背景執行緒上跑
    Thread.Sleep(1000);                   // 模擬耗時操作
    Console.WriteLine("背景工作完成");
});

Task<int> task2 = Task.Run(() =>         // 有回傳值的 Task
{
    int sum = 0;                          // 計算總和
    for (int i = 0; i < 1000000; i++)     // 大量計算
    {
        sum += i;                         // 累加
    }
    return sum;                           // 回傳結果
});

await task1;                              // 等待 task1 完成
int result = await task2;                 // 等待 task2 完成並取得結果
Console.WriteLine($"計算結果:{result}"); // 印出結果

📌 Task.WhenAll 與 Task.WhenAny

// Task.WhenAll — 等所有工作都完成
async Task DownloadAllAsync()  // 同時下載多個檔案
{
    Task<string> file1 = DownloadFileAsync("file1.txt"); // 開始下載 1
    Task<string> file2 = DownloadFileAsync("file2.txt"); // 開始下載 2
    Task<string> file3 = DownloadFileAsync("file3.txt"); // 開始下載 3

    // WhenAll 等所有 Task 都完成,回傳所有結果
    string[] results = await Task.WhenAll(file1, file2, file3);

    foreach (string r in results)     // 走訪所有結果
    {
        Console.WriteLine(r);         // 印出每個下載結果
    }
}

// Task.WhenAny — 等任何一個工作完成就好
async Task GetFastestAsync()  // 取得最快的回應
{
    Task<string> server1 = FetchFromServerAsync("https://server1.com");
    Task<string> server2 = FetchFromServerAsync("https://server2.com");

    // WhenAny 只要有一個完成就回傳
    Task<string> fastest = await Task.WhenAny(server1, server2);
    string result = await fastest;        // 取得最快完成的結果
    Console.WriteLine($"最快回應:{result}"); // 使用最快的結果
}

// 模擬下載檔案
async Task<string> DownloadFileAsync(string fileName)
{
    var random = new Random();
    int delay = random.Next(500, 2000);       // 隨機延遲 0.5-2 秒
    await Task.Delay(delay);                   // 模擬下載時間
    return $"{fileName} 下載完成(耗時 {delay}ms)"; // 回傳結果
}

// 模擬從伺服器取得資料
async Task<string> FetchFromServerAsync(string url)
{
    var random = new Random();
    await Task.Delay(random.Next(100, 1000));  // 模擬網路延遲
    return $"來自 {url} 的回應";              // 回傳回應
}

📌 HttpClient 非同步範例

// HttpClient 是 .NET 內建的 HTTP 客戶端
// 所有 HTTP 操作都是非同步的

async Task FetchWebPageAsync()  // 抓取網頁內容
{
    // 建議用 static 或 DI 管理 HttpClient(不要每次都 new)
    using HttpClient client = new HttpClient(); // 建立 HTTP 客戶端

    try // 網路操作可能失敗,要用 try-catch
    {
        // GetStringAsync 是非同步方法
        string content = await client.GetStringAsync("https://api.github.com");
        Console.WriteLine($"取得 {content.Length} 個字元"); // 印出內容長度

        // 也可以取得完整的 HttpResponseMessage
        HttpResponseMessage response = await client.GetAsync("https://api.github.com");
        if (response.IsSuccessStatusCode) // 檢查是否成功(200-299)
        {
            string body = await response.Content.ReadAsStringAsync(); // 讀取內容
            Console.WriteLine(body); // 印出回應內容
        }
        else
        {
            Console.WriteLine($"請求失敗:{response.StatusCode}"); // 印出錯誤狀態碼
        }
    }
    catch (HttpRequestException ex) // 捕捉 HTTP 相關的例外
    {
        Console.WriteLine($"網路錯誤:{ex.Message}"); // 印出錯誤訊息
    }
}

// POST 請求範例
async Task PostDataAsync()  // 送出資料
{
    using HttpClient client = new HttpClient();  // 建立客戶端

    var data = new { name = "小明", age = 20 };  // 要送出的資料
    string json = System.Text.Json.JsonSerializer.Serialize(data); // 轉成 JSON
    var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

    HttpResponseMessage response = await client.PostAsync("https://api.example.com/users", content);
    string result = await response.Content.ReadAsStringAsync(); // 讀取回應
    Console.WriteLine(result); // 印出結果
}

📌 ConfigureAwait(false)

// ConfigureAwait 控制 await 之後要在哪個執行緒繼續

// 預設:await 之後回到原來的執行緒(UI 執行緒)
async Task UpdateUIAsync()  // UI 應用程式中使用
{
    string data = await GetDataAsync();  // 預設會回到 UI 執行緒
    // label.Text = data;                // 可以安全地更新 UI
}

// ConfigureAwait(false):不需要回到原來的執行緒
async Task<string> GetDataFromApiAsync()  // 程式庫 / 非 UI 程式碼
{
    using HttpClient client = new HttpClient();
    // ConfigureAwait(false) 告訴系統:我不需要回到原來的執行緒
    // 這在程式庫中可以避免死鎖,也能提升效能
    string result = await client.GetStringAsync("https://api.example.com")
        .ConfigureAwait(false);  // 不需要回到原來的 context
    return result; // 處理結果
}

// 何時用 ConfigureAwait(false):
// ✅ 在程式庫(Library)中 — 幾乎都要用
// ✅ 在非 UI 的應用程式中
// ❌ 在 UI 事件處理器中不要用(否則不能更新 UI)
// ❌ 在 ASP.NET Core 中通常不需要(已經沒有 SynchronizationContext)

🤔 我這樣寫為什麼會錯?

❌ 錯誤:async void(最常見的錯誤!)

// ❌ 錯誤:不要用 async void(除了事件處理器)
async void BadMethod()  // async void 無法被 await
{
    await Task.Delay(1000);
    throw new Exception("這個例外無法被捕捉!"); // 會直接崩潰!
}

// ✅ 正確:用 async Task
async Task GoodMethod()  // async Task 可以被 await
{
    await Task.Delay(1000);
    throw new Exception("這個例外可以被 try-catch 捕捉");
}

// 唯一可以用 async void 的地方:UI 事件處理器
// async void Button_Click(object sender, EventArgs e) { ... }

❌ 錯誤:死鎖(Deadlock)

// ❌ 錯誤:在同步方法中用 .Result 或 .Wait() 等待非同步方法
void DeadlockExample()  // 同步方法
{
    // 在 UI 或 ASP.NET (非 Core) 中,這會造成死鎖!
    // var result = GetDataAsync().Result;  // ❌ 死鎖!
    // GetDataAsync().Wait();               // ❌ 死鎖!
}

// ✅ 正確:一路 async/await 到底("async all the way")
async Task CorrectExample()  // 非同步方法
{
    var result = await GetDataAsync();  // ✅ 用 await
    Console.WriteLine(result);
}

// 如果真的必須在同步中呼叫非同步方法:
void WorkaroundExample()
{
    // 用 Task.Run 包裝(避免在原來的 context 上等待)
    var result = Task.Run(() => GetDataAsync()).Result;
}

❌ 錯誤:忘記 await

// ❌ 錯誤:忘記 await,方法會立刻回傳,不等結果
async Task ForgotAwait()
{
    // Task.Delay(5000);  // ❌ 沒有 await,不會等 5 秒
    // 上面的 Task.Delay 被忽略了,程式直接往下跑

    await Task.Delay(5000);  // ✅ 有 await,會等 5 秒
}

❌ 錯誤:在迴圈中逐一 await

// ❌ 效率差:一個一個等
async Task SlowVersion()
{
    var urls = new[] { "url1", "url2", "url3" };
    foreach (var url in urls)
    {
        await FetchFromServerAsync(url);  // 等完一個才做下一個
    }
    // 如果每個要 1 秒,總共要 3 秒
}

// ✅ 效率好:同時進行
async Task FastVersion()
{
    var urls = new[] { "url1", "url2", "url3" };
    var tasks = urls.Select(url => FetchFromServerAsync(url)); // 同時開始
    await Task.WhenAll(tasks);  // 等所有完成
    // 如果每個要 1 秒,總共只要 1 秒(同時進行)
}

💡 大家的想法 · 0

載入中...
💬 即時聊天室 🟢 0 人在線
😀 😎 🤓 💻 🎮 🎸 🔥
➕ 新問題
📋 我的工單
💬 LINE 社群
🔒
需要註冊才能使用此功能
註冊帳號即可解鎖測驗、遊戲、簽到、筆記下載等所有功能,完全免費!
免費註冊