Async/await

Trong lập trình máy tính, mô thức async/await là một tính năng cú pháp của nhiều ngôn ngữ lập trình, cho phép cấu trúc nên hàm 'bất đồng bộ, non-blocking' theo lối y hệt ở vẻ ngoài như hàm đồng bộ thông thường. Về mặt ngữ nghĩa, nó gần gũi với khái niệm coroutine và thường hay được thực hiện bằng kỹ thuật tương tự, và chủ yếu là nhằm tạo cơ hội để cho chương trình thực thi phần code khác trong khi chờ đợi tác vụ bất đồng bộ, chạy lâu dài nào đó hoàn thành, tác vụ như thế thường được biểu trưng bằng promise hoặc cấu trúc dữ liệu tương tự. Tính năng này hiện diện trong C# 5.0, C++ 20, Python 3.5, F#, Hack, Julia, Dart, Kotlin 1.1, Rust 1.39, Nim 0.9.4, JavaScript ES2017, Swift 5.5 và Zig, và trong một số công trình thử nghiệm trong các bản mở rộng, các phiên bản beta và các bản thực hiện nhất định của Scala.
Lịch sử.
Vào năm 2007, F# đã thêm workflow bất đồng bộ cùng với cú pháp await trong phiên bản 2.0. Điều này ảnh hưởng đến cơ chế async/await được thêm vào C# sau này.
Microsoft đã phát hành phiên bản C# kèm với cú pháp async/await lần đầu tiên trong bản Async CTP (2011). Và sau này phát hành chính thức trong C# 5 (2012).
Nhà phát triển dẫn dắt của Haskell, Simon Marlow, đã tạo ra package bất đồng bộ vào năm 2012.
Python đã thêm hỗ trợ async/await trong phiên bản 3.5 vào năm 2015, thêm 2 từ khóa mới là codice_1 và codice_2.
TypeScript đã thêm hỗ trợ cho async/await trong phiên bản 1.7 vào năm 2015.
Javascript đã thêm hỗ trợ cho async/await vào năm 2017 làm một phần của bản JavaScript ECMAScript 2017.
Rust đã thêm hỗ trợ cho async/await trong phiên bản 1.39.0 vào năm 2019 với 1 từ khóa mới là codice_1 và mô thức lazy evaluation cho await.
C++ đã thêm hỗ trợ async/await trong phiên bản 20 vào năm 2020 với 3 từ khóa mới là codice_4, codice_5 và codice_6.
Swift đã thêm hỗ trợ async/await trong phiên bản 5.5 vào năm 2021, thêm 2 từ khóa mới là codice_1 và codice_2. Phiên bản này được phát hành cùng với bản thực hiện cụ thể cho Mô hình Actor với từ khóa codice_9 trong đó sử dụng async/await để điều hòa truy cập từ bên ngoài vào từng actor.
Ví dụ C#.
Hàm C# bên dưới tải xuống tài nguyên từ URI và trả về độ dài của tài nguyên, sử dụng mô thức async/await như sau:
public async Task&lt;int&gt; FindPageSizeAsync(Uri uri) 
 byte[] data = await new HttpClient().GetByteArrayAsync(uri);
 return data.Length;
Hàm mà sử dụng async/await thì có thể dùng bao nhiêu biểu thức codice_2 cũng được tùy ý muốn, và mỗi biểu thức như thế sẽ được xử trí theo cùng cách (thông qua một promise sẽ chỉ được trả về cho caller cho lần await đầu tiên, còn các lần await sau sẽ tận dụng callback nội bộ). Hàm cũng có thể tự tay giữ lại đối tượng promise và đi xử lý việc khác trước (bao gồm cả việc khởi động những tác vụ bất đồng bộ khác), trì hoãn việc await promise đấy cho đến khi nào cần đến kết quả của promise đấy thì mới thôi. Hàm mà dùng promise thì cũng có phương thức kết tập promise cho phép ta await nhiều promise cùng một lúc hay trong một số mô thức đặc biêt (chẳng hạn như codice_29 trong .NET có chức năng trả về một codice_19 không có giá trị, nó phân giải khi tất cả tác vụ trong đối số của codice_29 được phân giải xong). Nhiều kiểu promise cũng có các tính năng vượt xa những điều mà mô thức async/await hay dùng, chẳng hạn có thể thiết đặt nhiều hơn một callback kết quả hay có thể tra duyệt tiến trình của tác vụ nào mà chạy lâu cá biệt.
Trong trường hợp đặc thù như C#, và trong nhiều ngôn ngữ có tính năng này, thì mô thức async/await không phải là một phần cốt lõi trong runtime của ngôn ngữ, mà thay vào đó được thực hiện bằng lambda hoặc được thực hiện bằng 'phần tiếp tục' tại compile time. Thí dụ, trình biên dịch C# khả năng sẽ dịch đoạn code bên trên thành thứ gì đó như sau đây trước khi dịch nó thành định dạng bytecode IL:
public Task&lt;int&gt; FindPageSizeAsync(Uri uri) 
 Task&lt;byte[]&gt; dataTask = new HttpClient().GetByteArrayAsync(uri);
 Task&lt;int&gt; afterDataTask = dataTask.ContinueWith((originalTask) =&gt; {
 return originalTask.Result.Length;
 return afterDataTask;
Bởi vì điều này, nếu phương thức giao diện mà cần trả về đối tượng promise, nhưng bản thân nó lại không yêu cầu codice_2 trong phần thân để chờ đợi bất kì tác vụ bất đồng bộ nào, thì nó cũng chẳng cần đến từ khóa tu sức codice_1 và thay vào đó có thể trả về đối tượng promise trực tiếp luôn. Thí dụ, hàm có thể đưa ra một promise mà ngay lập tức phân giải thành giá trị kết quả nào đó (chẳng hạn như codice_34 của C#), hoặc nó có thể đơn giản trả về một promise khác mà vô hình trung chính là promise cần.
Tuy nhiên có một điểm quan trọng cần lưu tâm của chức năng này, đó là mặc dù phần code trông giống như code blocking xưa giờ, thì thực ra bản chất nó là code non-blocking và còn có khả năng là đa thread nữa, nghĩa là nhiều sự kiện khác có thể xen vào trong khi chờ đợi promise được phân giải bằng codice_2. Thí dụ, đoạn code sau, tuy luôn luôn chạy thành công nếu viết lại theo mô hình blocking mà không dùng codice_2, nhưng theo đúng thế này thì lại có thể bị các sự kiện bên ngoài can thiệp vào trong quá trình codice_2 và do đó có thể xảy ra tình cảnh nội dung trong codice_38 chung bị thay đổi khi code chạy đến phần bên dưới codice_2.
var a = state.a;
var data = await new HttpClient().GetByteArrayAsync(uri);
Debug.Assert(a == state.a); // Có khả năng là không được vì giá trị của state.a
 // có thể đã bị handler của sự kiện xen giữa nào đó thay đổi rồi.
return data.Length;
Trong F#.
F# đã thêm workflow bất đồng bộ trong phiên bản 2.0. Workflow bất đồng bộ này được thực hiện thông qua biểu thức computation. Ta có thể định nghĩa nó mà không cần chỉ định bất kì ngữ cảnh đặt biệt nào (như codice_1 trong C#). Trong workflow bất đồng bộ của F# thì thêm dấu cảm (!) vào từ khóa để bắt đầu tác vụ bất đồng bộ.
Hàm bất đồng bộ sau đây tải xuống dữ liệu từ URL thông qua workflow bất đồng bộ:
let asyncSumPageSizes (uris: #seq&lt;Uri&gt;) : Async&lt;int&gt; = async {
 use httpClient = new HttpClient()
 let! pages = 
 uris
 |&gt; Seq.map(httpClient.GetStringAsync » Async.AwaitTask)
 |&gt; Async.Parallel
 return pages |&gt; Seq.fold (fun accumulator current -&gt; current.Length + accumulator) 0
Trong C#.
Mô thức async/await được Microsoft đưa vào C# kể từ phiên bản 5.0 với tên gọi là 'Mô thức bất đồng bộ dựa trên Task'. Phương thức bất đồng bộ cần phải trả về một trong các kiểu codice_41, codice_19, codice_12, và codice_44 (kiểu cuối mới được thêm vào từ phiên bản 7.0). Phương thức bất đồng bộ mà trả về codice_41 là nhằm dùng cho event handler; trong hầu hết trường hợp khi phương thức không cần phải trả về giá trị gì (tức codice_41), thì thay vào đó nên trả về codice_19 vì làm vậy cho phép việc xử trí ngoại lệ trực quan hơn.
Phương thức mà có sử dụng codice_2 thì phải được khai báo với từ khóa codice_1. Trong phương thức khai báo bằng codice_1 mà có kiểu giá trị trả về là codice_12 thì trong đó phải có return statement của kiểu gán được vào codice_52 chứ không phải là vào codice_12; trình biên dịch sẽ gói giá trị codice_52 đấy vào kiểu generic codice_12. Bất kì phương thức nào mà có kiểu trả về codice_19 hoặc codice_12 thì cũng có thể gọi phương thức đó với từ khóa codice_2 được, không nhất thiết phương thức đó phải được khai báo bằng codice_1.
Phương thức bất đồng bộ sau đây tải xuống dữ liệu từ URL bằng codice_2. Bởi vì phương thức này duyệt qua toàn bộ uri và phát đi tác vụ cho mỗi uri xong hẳn rồi mới chờ các tác vụ đấy hoàn thành bằng từ khóa codice_2, nên tập tài nguyên có thể được tải đồng thời thay vì phải chờ đợi phần tài nguyên cuối hoàn tất trước khi bắt đầu tải phần tiếp theo.
public async Task&lt;int&gt; SumPageSizesAsync(ICollection&lt;Uri&gt; uris) 
 var client = new HttpClient();
 var loadUriTasks = new List&lt;Task&lt;byte[]»();
 foreach (var uri in uris)
 var loadUriTask = client.GetByteArrayAsync(uri);
 loadUriTasks.Add(loadUriTask);
 
 var total = 0;
 foreach (var loadUriTask in loadUriTasks)
 statusText.Text = $"Tìm thấy {total} byte ...";
 var resourceAsBytes = await loadUriTask;
 total += resourceAsBytes.Length;
 statusText.Text = $"Tìm thấy {total} byte cả thảy";
 return total;
Trong Scala.
Trong bản mở rộng thử nghiệm Scala-async cho Scala, codice_2 là một "phương thức", mặc dù nó không vận hành giống như phương thức bình thường. Hơn nữa, không giống như ở C# 5.0 trong đó phương thức phải được đánh dấu là bất đồng bộ, trong Scala-async, ta có "khối" code được bao quanh bằng "lời gọi" bất đồng bộ.
Cách nó hoạt động.
Trong Scala-async, codice_1 thực ra được thực hiện thông qua Scala macro, khiến trình biên dịch phát sinh code khác hoàn toàn, và tạo ra bản thực hiện dựa trên máy trạng thái hữu hạn (được coi là có hiệu quả hơn bản thực hiện dựa trên monad, song viết bằng tay thì không tiện bằng).
Có mấy kế hoạch nhằm để Scala-async hỗ trợ nhiều loại thực hiện khác nhau, bao gồm cả loại phi bất đồng bộ.
Trong Python.
Python 3.5 (2015) đã thêm hỗ trợ cho async/await như đã mô tả trong PEP 492 (https://www.python.org/dev/peps/pep-0492/).
import asyncio
async def main():
 print("hello")
 await asyncio.sleep(1)
 print("world")
asyncio.run(main())
Trong JavaScript.
Toán tử await trong Javascript chỉ có thể được dùng ở bên trong hàm bất đồng bộ. Nếu tham số là promise thì sự thực thi của hàm bất đồng bộ đấy sẽ tiếp tục khi promise đấy được phân giải (song trong trường hợp nếu như promise bị từ chối, thì một lỗi sẽ được ném ra và có thể xử trí lỗi đó thông qua cơ chế xử trí ngoại lệ của Javascript). Nếu tham số đấy không phải là promise thì tham số đấy bản thân nó sẽ được trả về ngay lập tức.
Nhiều thư viện cung cấp đối tượng promise có thể dùng được với await, miễn là nó khớp với đặc tả promise bản địa của Javascript. Tuy nhiên, promise từ thư viện jQuery thì không tương thích với tiêu chuẩn Promises/A+ mãi đến bản jQuery 3.0.
Đây là một ví dụ (được điều chỉnh lại từ bài viết này):
async function createNewDoc() {
 let response = await db.post({}); // đăng tài liệu mới
 return db.get(response.id); // tìm bằng id
async function main() {
 try {
 let doc = await createNewDoc();
 console.log(doc);
 } catch (err) {
 console.log(err);
main();
Trong C++.
Trong C++, await (có tên là co_await trong C++) đã chính thức được hợp nhất vào . Coroutine hỗ trợ cho nó, cùng với các từ khóa như codice_5 thì sẵn có trong hai trình biên dịch và còn thì mới chỉ hỗ trợ một phần.
Đáng lưu ý rằng std::promise và std::future, mặc dù có vẻ sẽ là hai loại đối tượng khả await, nhưng lại không hề thực hiện những cơ chế cần thiết để khi trả về chúng từ coroutine thì có thể await lên chúng được bằng co_await. Lập trình viên phải tự thực hiện một số lượng các hàm thành viên public, chẳng hạn như codice_65, codice_66, và codice_67 lên kiểu trả về để kiểu đó có thể await lên được. Có thể xem chi tiết tại cppreference.
using namespace std;
CustomAwaitableTask&lt;int&gt; add(int a, int b)
 int c = a + b;
 co_return c;
CustomAwaitableTask&lt;int&gt; test()
 int ret = co_await add(1, 2);
 cout « "return " « ret « endl;
 co_return ret;
int main()
 auto task = test();
 return 0;
Trong C.
Chưa có hỗ trợ chính thức cho async/await trong ngôn ngữ C. Một số thư viện coroutine chẳng hạn như s_task mô phỏng từ khóa await/async bằng macro.
// định nghĩa bộ nhớ stack cho các tác vụ
int g_stack_main[64 * 1024 / sizeof(int)];
int g_stack0[64 * 1024 / sizeof(int)];
int g_stack1[64 * 1024 / sizeof(int)];
void sub_task(__async__, void* arg) {
 int i;
 int n = (int)(size_t)arg;
 for (i = 0; i &lt; 5; ++i) {
 printf("task %d, delay seconds = %d, i = %d\n", n, n, i);
 s_task_msleep(__await__, n * 1000);
 //s_task_yield(__await__);
void main_task(__async__, void* arg) {
 int i;
 // tạo hai tác vụ con
 s_task_create(g_stack0, sizeof(g_stack0), sub_task, (void*)1);
 s_task_create(g_stack1, sizeof(g_stack1), sub_task, (void*)2);
 for (i = 0; i &lt; 4; ++i) {
 printf("task_main arg = %p, i = %d\n", arg, i);
 s_task_yield(__await__);
 // đợi cho đến khi các tác vụ con kết thúc
 s_task_join(__await__, g_stack0);
 s_task_join(__await__, g_stack1);
int main(int argc, char* argv) {
 s_task_init_system();
 // tạo tác vụ chính
 s_task_create(g_stack_main, sizeof(g_stack_main), main_task, (void*)(size_t)argc);
 s_task_join(__await__, g_stack_main);
 printf("all task is over\n");
 return 0;
Trong Perl 5.
Module Future::AsyncAwait là chủ đề của một khoản chi cho Quỹ Perl vào tháng 9 năm 2018.
Trong Rust.
Vào ngày 7 tháng 11 năm 2019, async/await được phát hành vào phiên bản ổn định của Rust. Hàm bất đồng bộ trong Rust được biến đổi từ cú pháp tiện lợi sang hàm số có trả về giá trị có thực hiện trait Future. Hiện nay nó được thực hiện bằng máy trạng thái hữu hạn.
// Trong file Cargo.toml của crate, ta cần `futures = "0.3.0"` trong mục các dependency
// để cho chúng ta có thể sử dụng crate futures
extern crate futures; // Hiện nay không có executor nào trong thư viện `std` cả.
// Cái này sẽ "khử đường" (desugar) về thành cái giống như
// `fn async_add_one(num: u32) -&gt; impl Future&lt;Output = u32&gt;`
async fn async_add_one(num: u32) -&gt; u32 {
 num + 1
async fn example_task() {
 let number = async_add_one(5).await;
 println!("5 + 1 = {}", number);
fn main() {
 // Tạo ra Future sẽ không khởi động execution
 let future = example_task();
 // `Future` chỉ thực thi khi ta thực sự poll nó chứ không như Javascript.
 futures::executor::block_on(future);
Trong Swift.
Swift 5.5 (2021) đã thêm hỗ trợ cho async/await như đã mô tả trong SE-0296 (https://github.com/apple/swift-evolution/blob/main/proposals/0296-async-await.md).
func getNumber() async throws -&gt; Int {
 try await Task.sleep(nanoseconds: 1_000_000_000)
 return 42
Task {
 let first = try await getNumber()
 let second = try await getNumber()
 print(first + second)
Lợi ích và phê phán.
Lợi điểm đáng kể của mô thức async/await trong những ngôn ngữ có hỗ trợ nó là nó tạo điều kiện viết code 'bất đồng bộ, non-blocking' với overhead tối thiểu mà lại trông hầu như giống hệt code 'đồng bộ, blocking' xưa giờ. Nói cụ thể, người ta cho rằng await là cách tốt nhất để viết code bất đồng bộ trong những chương trình có sử dụng kỹ thuật truyền thông điệp; hơn nữa, do gần với kiểu code blocking, nên await được nêu là mang ưu điểm dễ đọc và chỉ cần lượng boilerplate code tối thiểu. Vì thế, async/await giúp cho lập trình viên dễ suy lý về chương trình của mình hơn, và await có xu hướng thúc đẩy nên code non-blocking tốt hơn, kiên cố hơn trong những ứng dụng cần đến nó. Các chương trình như vậy trải dài từ chương trình biểu thị giao diện đồ họa người dùng đến những chương trình stateful phía server có khả năng nới quy mô đến mức đồ sộ, chẳng hạn như game và ứng dụng tài chính.
Còn khi phê phán await, người ta cho thấy rằng await có xu hướng khiến cho code bao quanh phải trở nên bất đồng bộ luôn; về mặt khác, người ta biện luận rằng bản tính "truyền nhiễm" này của code await (có khi được đem so sánh với "vi-rút zombie") thực ra là điều cố hữu trong tất cả các loại lập trình bất đồng bộ, nên await như vậy không hề có gì độc lạ ở đây cả.