非同步程式設計經歷了 回調地獄 → Promise/Future → Async/Await 三代演進。Async/Await 是語言層面的語法糖,讓非同步代碼像同步一樣線性閱讀,本質上仍是 狀態機 + 續體傳遞。
| 語言 | 關鍵字 | 返回型別 | 底層機制 | 執行緒模型 |
|---|---|---|---|---|
| C# | async / await | Task / Task<T> | 編譯器生成狀態機 (IAsyncStateMachine) | ThreadPool + SynchronizationContext |
| Java | 無原生關鍵字 | CompletableFuture / Future | 庫層面實現 (Quasar、Loom 虛擬執行緒) | ForkJoinPool / Virtual Threads (JDK 21+) |
| PHP | async / await (Swoole/ReactPHP) | Promise / Coroutine | 協程 + 事件循環 | 單執行緒事件循環 + 協程調度 |
public async Task<string> DownloadAsync(string url) {
using var client = new HttpClient();
var data = await client.GetStringAsync(url); // 釋放執行緒,完成後續體回 ThreadPool
return Process(data);
}
// 呼叫端
var task = DownloadAsync("https://api.example.com");
Console.WriteLine("發起請求,主執行緒繼續做其他事...");
var result = await task; // 這裡同步等待結果
Console.WriteLine(result);
Java 尚無語法級 Async/Await(JEP 425 虛擬執行緒為鋪路),主流方案:
Thread.startVirtualThread(() -> { ... }),以同步寫法獲極高並發。ExecutorService exec = Executors.newVirtualThreadPerTaskExecutor(); // JDK 21
CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> httpGet(url1), exec);
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> httpGet(url2), exec);
CompletableFuture<Void> both = CompletableFuture.allOf(f1, f2);
both.thenRun(() -> {
System.out.println("結果1: " + f1.join());
System.out.println("結果2: " + f2.join());
}).join();
// 工具類
public final class Await {
public static <T> T await(Future<T> future, long timeout, TimeUnit unit) {
try { return future.get(timeout, unit); }
catch (TimeoutException e) { future.cancel(true); throw new RuntimeException(e); }
catch (Exception e) { throw new RuntimeException(e); }
}
}
// 使用
FutureTask<String> task = new FutureTask<>(() -> httpGet(url));
new Thread(task).start();
String result = Await.await(task, 5, TimeUnit.SECONDS);
Co\run(function () {
$chan = new Chan(2);
go(function () use ($chan) { $chan->push(file_get_contents("https://api1")); });
go(function () use ($chan) { $chan->push(file_get_contents("https://api2")); });
$data1 = $chan->pop();
$data2 = $chan->pop();
echo "Done: $data1, $data2\n";
});
| 優點 | 缺點/陷阱 |
|---|---|
| 代碼線性可讀,易除錯 | 隱藏非同步邊界,易誤用同步阻塞 API 導致效能退化 |
| 例外處理統一 (try/catch) | 若未正確 await,異常會被吞沒在 Task 中 |
| 易於組合並行/序列流程 | 過度使用導致大量狀態機物件,記憶體壓力 |
| 語言級支援(C#、JS、Python、Rust) | Java 需靠庫/虛擬執行緒,語法不如原生優雅 |
Thread.sleep、jdbc、httpClient.execute),改用非同步對應版本。SemaphoreSlim / CompletableFuture.allOf / ExecutorService 限制併發度,防 OOM。ConfigureAwait(false) (C#) 或虛擬執行緒 (Java) 避免死鎖。Java Project Loom (JDK 21 已正式納入虛擬執行緒) 將徹底改寫非同步編程體驗:以同步寫法、極低成本實現百萬級併發,或許未來會引入原生 async/await 語法糖。在此之前,虛擬執行緒 + CompletableFuture 是最佳實踐。