Asynchronous Programming - #3 Mô hình lập trình tác vụ bất đồng bộ
Bạn có thể tránh tình trạng quá tải và cải thiện độ phản hồi của toàn bộ ứng dụng bằng cách sử dụng lập trình bất đồng bộ. Tuy nhiên, những kỹ thuật truyền thống khi lập trình ứng dụng bất đồng bộ có thể phức tạp, khó viết, debug và bảo trì.
C# 5 giớ thiệu một cách tiếp cận đơn giản hơn, lập trình bất đồng bộ, mà thúc đẩy hỗ trợ bất đồng bộ trong .NET Framework 4.5 và cao hơn nữa, .NET Core và Windows Runtime. Trình biên dịch làm công việc khó mà lập trình viên đã từng làm, và ứng dụng của bạn vẫn giữ được cấu trúc logic giống với mã code không đồng bộ. Kết quả là, bạn sẽ có được tất cả lợi ích của lập trình bất đồng bộ với nỗ lực nhỏ.
Chủ đề này cung cấp một cái nhìn tổng quát sử dụng dụng lập trình bất khi nào và như thế nào và bao gồm những đường dẫn tới những chủ đề liên quan chi tiết hơn và có ví dụ luôn.
Async cải thiện khả năng phản hồi
Bất đồng bộ là cần thiết cho các hoạt động có khả năng bị chặn như là truy cập web. Truy cập nguồn tài nguyên của web là chậm hoặc là bị hoãn. Nếu một hoạt động bị chặn trong tiến trình đồng bộ, cả ứng dụng phải chờ. Nếu dùng bất đông bộ, ứng dụng có thể tiếp tục với công việc khác mà không phụ thuộc vào tài liệu web cho đến khi những luồng có tiềm năng bị chặn hoàn thành.
Bảng dưới đây cho thấy các lĩnh vực mà lập trình bất đồng bộ cải thiện khả năng phản hồi. Các APIs từ .NET và Windows Runtime bao gồm những phương thức hỗ trợ lập trình bất đồng bộ.
Lĩnh vực ứng dụng | Các kiểu .NET với những phương thức bất đồng bộ | Các kiểu Windows Runtime với những phương thức bất đồng bộ |
Truy cập web | HttpClient | Windows.Web.Http.HttpClientSyndicationClient |
Làm việc với files | JsonSerializer StreamReader StreamWriter XmlReader XmlWriter | StorageFile |
Làm việc với hình ảnh | MediaCapture BitmapEncoder BitmapDecoder | |
Lập trình WCF | Synchronous and Asynchronous Operations |
Bất đồng bộ chứng mình một giá trị đặc biệt cho ứng đụng truy cập luồng UI bởi vì tất cả hoạt động liên quan tới UI thường chia sẻ trong một luồng. Nếu bất kỳ tiến trình nào bị chặn trong một ứng dụng đồng bộ, tất cả sẽ bị chặn. Ứng dụng của bạn dừng phản hồi, và bạn có thể kết luận là nó thất bại thay vì chỉ chờ.
Khi bạn sử dụng các phương thức bất đồng bộ, ứng dụng tiếp tục để phản hồi đến UI. Bạn có thể thay đổi hoặc thu nhỏ màn hình, ví dụ, hoặc bạn có thể đóng ứng dụng nếu bạn không muốn chờ nó chạy xong.
Cách tiếp cận dựa trên bất đồng bộ thêm một hộp số tự động tương đương vào danh sách lựa chọn mà bạn có thể chọn từ hoạt động thiết kế bất đồng bộ. Nghĩa là bạn có tất cả lợi ích của lập trình bất đồng bộ truyền thống những tốn ít nỗ lực từ lập trình viên.
Phương thức Async dễ viết
Từ khóa async
và await
trong C# là mấu chốt của lập trình bất đồng bộ. Sử dụng hai từ khóa đó thôi là bạn có thể sử dụng resource trong .NET Framework, .NET Core và Windows Runtime để tạo một phương thức bất đồng bộ dễ như cách viết một phương thức đồng bộ vậy. Những phương thức bất đồng bộ bạn có thể định nghĩ bằng cách sử dụng từ khóa async
để gọi phương thức bất đồng bộ.
Dưới đây là ví dụ về phương thức bất đồng bộ. Phần lớn code sẽ giống như bình thường vậy á.
Bạn có thể tìm thấy một ví dụ Windows Presentation Foundation (WPF) hoàn chỉnh có thể tài về từ Lập trình bất đồng bộ với async và await trong C#
Bạn có thể học một vài practice từ ví dụ trên. Bắt đầu với dấu hiệu nhận biết phương thức. Nó có modifier async
. Kiểu trả về là Task<int>
(Xem thêm mục Reture Types để có thêm sự lựa chọn). Tên phương thức kết thúc bằng từ Async
. Trong body của phương thức, GetStringAsync
trả về một Task<string>
. Nghĩa là khi bạn await
task
sẽ lấy được một string
(contents
). Trước khi chờ task, bạn có thể làm việc khác mà nó không xài cái string
từ GetStringAsync
.
Hãy tập trung vào operator await
. Nó hoãn GetUrlContentLengthAsync
:
GetUrlContentLengthAsync
không thể tiếp tục cho tới khigetStringTask
hoàn thành- Trong lúc đó, control trả về gọi
GetUrlContentLengthAsync
- Control tiếp tục ở đây khi
getStringTask
hoàn thành await
operator sau đó nhận được kết quảstring
từgetStringTask
Câu lệnh trả về xác định kết quả số nguyên. Bất kỳ phương thức đang chờ GetUrlContentLengthAsync
đạt được giá trị độ dài.
Nếu GetUrlContentLengthAsync không có bất kỳ công việc nào nó có thể làm giữa GetStringAsync và chờ cho tới khi nó hoàn thành, bạn có thể đơn giản hóa code của bạn bằng cách gọi hoặc chờ những bằng câu lệnh dưới đây.
Tổng kết những đặc điểm của phương thức bất đồng bộ từ ví dụ trên:
-
Phương thức ký hiệu có modifier là async
-
Tên của phương thức bất đồng bộ, theo quy ước, kết thúc với hậu tố Async
-
Kiểu trả về là một trong những loại sau:
Task<TResult>
nếu phương thức của bạn có một giá trị trả về và nó type làTResult
.Task
nếu phương thức của bạn không có giá trị trả về hoặc trả về câu lệnh không có toán hạngvoid
nếu bạn đang viết một async event handler- Bất kỳ kiểu khác có một phương thức GetAwaiter (bắt đầu từ C# 7.0)
Để có nhiều thông tin, hãy xem mục Return types and parameters
-
Phương thức thường bao gồm ít nhất một biểu thức
await
, mà nó đánh dấu một điểm để phương thức không thể tiếp tục cho tới khi hoạt động bất đồng bộ hoàn thành. Trong lúc đó, phương thức hoãn lại, và control trả về cho caller của phương thức. Phần kế tiếp của chủ đề này sẽ minh họa việc gì xảy ra ở lúc hoãn lại đó.
Trong phương thức bất đồng bộ, bạn sử dụng từ khóa và kiểu được cung cấp để chỉ ra những cái bạn muốn làm, và trình biên dịch làm phần còn lại, bao gồm việc theo dõi cái gì xảy ra khi control trở lại ở lúc chờ trong phương thức đang bị hoãn. Một số quy trình thông thường, chẳng hạn như vòng lặp và xử lý ngoại lệ, có thể khó để xử lý với code bất đồng bộ truyền thống. Trong một phương thức bất đồng bộ, bạn viết những thành phần nhiều như bạn đang trong lúc viết code đồng bộ, và vấn đề được giải quyết.
Để có thêm nhiều thông xin về bất đồng bộ của những phiên bản của .NET Framework, xem TPL and traditional .NET Framwork asynchronous programming.
Chuyện gì xảy ra trong một phương thức bất đồng bộ
Điều quan trong nhất cần phải hiểu trong lập trình bất đồng bộ là cách các luồng control di chuyển từ phương thức tới phương thức như thế nào. Sơ đồ dưới đây sẽ dẫn bạn đi qua quá trình đó:Những con số trong sơ đồ tương ứng với thứ tự các bước, bắt đầu khi một phương thức gọi một phương thức bất đồng bộ.
-
Một phương thức gọi và chờ phương thức bất đồng bộ
GetUrlContentLengthAsync
-
GetUrlContentLengthAsync
tạo một thực thểHttpClient
và gọi phương thức bất đồng bộGetStringAsync
để tải nội dung của website dưới dạng chuỗi -
Có gì đó xảy ra trong
GetStringAsync
hoãn lại quá trình của nó. Có thể nó phải chờ website để tải hoặc một hoạt động chặn gì đó. Để tránh chặn nguồn tài nguyên,GetStringAsync
trả về lại cho cái gọi nó làGetUrlContentLengthAsync
GetStringAsync
trả về mộtTask<TResult>
,TResult
là một chuỗi, vàGetUrlContentLengthAsync
gán task vô biếngetStringTask
. Task đại diện cho quá trình đang chạy khi gọiGetStringAsync
, với một cam kết là tạo ra giá trị thực sự khi nó hoàn thành -
Bởi vì
getStringTask
chưa có await,GetUrlContentLengthAsync
có thể làm công việc khác mà công việc đó không cần kết quả cuối cùng từGetStringAsync
. Công việc đó được biểu diễn bằng cách gọi một phương thưsc đồng bộDoIndepentWork
. -
DoIndependentWork
là một phương thức đồng bộ mà nó làm công việc của nó và trả kết quả về cho chỗ gọi đó. -
GetUrlContentLengthAsync
đã làm xong việc nó có thể làm mà không cần kết quả từgetStringTask
.GetUrlContentLengthAsync
kế tiếp muốn tính toán và trả về kết quả độ dài của string vừa tải về được, nhưng phương thức không thể tính toán giá trị cho đến khi phương thức có chuỗiVì thế,
GetUrlContentLengthAsync
sử dụng một toán tử await để hoãn quá trình của nó và mang control tới phương thức mà nó gọiGetUrlContentLengthAsync
.GetUrlContentLengthAsync
trả về mộtTask<int>
cho chỗ gọi nó. Task đại diện một lời hứa để tạo ra kết quả số nguyên là độ dài của chuỗi được tải về.Lưu ý Nếu
GetStringAsync
(và do đógetStringTask
) hoàn thành trước khiGetUrlContentLengthAsync
chờ nó, control vẫn nằm trongGetUrlContentLengthAsync
. Chi phí tạm dừng và sau đó trả về choGetUrlContentLengthAsync
sẽ bị lãng phí nếu quá trình bất đồng bộgetStringTask
đã hoàn thành vàGetUrlContentLengthAsync
không phải chờ đến kết quả cuối cùng.Trong phương thức gọi, những chỗ khác vẫn tiếp tục. Nó sẽ làm tiếp tục công việc khác mà công việc đó không phụ thuộc vào kết quả từ
GetUrlContentLengthAsync
trước khi chờ kết quả, hoặc caller có thể chờ ngay lập tức. Phương thức gọi đang chờGetUrlContentLengthAsync
, vàGetUrlContentLengthAsync
đang chờGetStringAsync
. -
GetStringAsync
hoàn thành và tạo ra một kết quả chuỗi. Kết quả chuỗi không được trả về bởi việc gọiGetStringAsync
theo cách bạn mong đợi. (Hãy nhớ phương thức đã trả về ở bước thứ 3) Thay vào đó, kết quả chuỗi được trữ lại trong task thể hiện sự hoàn thành của phương thức,getStringTask
. Toán tử await lấy kết quả từgetStringTask
. Câu lệnh gán kết quả đạt được vàocontents
-
Khi
GetUrlContentLengthAsync
có kết quả chuỗi, phương thức có thể tính toán độ dài của chuỗi. Sau đó công việc củaGetUrlContentLengthAsync
cũng hoàn thành, và trình xử lý sự kiện đang chờ để có thể tiếp tục. Trong ví dụ đầy đủ ở cuối chủ đề, bạn có thể xác nhận rằng trình xử lý sự kiện lấy và in giá trị độ dài. Nếu chưa quen với lập trình bất đồng bộ, hãy dành vài phút để xem xét sự khác nhau về hành vi giữa đồng bộ và bất đồng bộ. Phương thức đồng bộ trả về khi nó hoàn toàn hoàn thành (bước 5), nhưng phương thức bất đồng bộ trả về một giá trị task khi nó bị hoãn (bước 3 và 6). Khi phương thức bất đồng bộ cuối cùng hoàn thành việc của nó, task được đánh dấu là hoàn thành và có kết quả, nếu có, được lưu trữ trong task.
Các phương thức API có async
Bạn có thể tự hỏi chỗ nào để tìm kiếm các phương thức như GetStringAsync mà hỗ trợ lập trình bất đồng bộ. NETFramework 4.5 hoặc cao hơn và .NET Core có những thành viên mà nó làm việc với async và await. Bạn có thể nhận thấy chúng bằng hậu tố Async được thêm cho mỗi thành viên, và bằng kiểu trả về của Task
hoặc Task<TResult>
. Ví dụ, lớp System.IO.Stream bao gồm các phương thức như CopyToAsync
, ReadAsync
và WriteAsync
tương ứng với phương thức đồng bộ CopyTo
, Read
và Write
.
Windows Runtime cũng có nhiều phương thức mà bạn có thể sử dụng async và await trong các ứng dụng Windows. Để biết thêm thông tin, hãy xem Threading and async programming cho lập trình UWP và Asynchronous programming (Windows Store apps) và Quickstart: Calling asynchronous APIs trong C# hoặc Visual Basic nếu bạn sử dụng những phiên bản mới nhất của Windows Runtime.
Threads (Luồng)
Phương thức bất đồng bộ có mục đích không chặn các hoạt động. Một biểu thức await trong phương thức bất đồng bộ không chặn luồng hiện tại trong khi task cần chờ vẫn đang chạy. Thay vào đó, biểu thức đăng ký phần còn lại của phương thức như một phần tiếp tục và trả quyền điều khiển cho chỗ gọi phương thức bất đồng bộ.
Từ khóa async và await không tạo ra những luồng mới. Phương thức bất đồng bộ không yêu cầu đa luồng bởi vì một phương thức bất đồng bộ không chạy trên luồng riêng của nó. Phương thức chạy trên một ngữ cảnh đồng bộ và sử dụng thời gian trên luồng chỉ khi phương thức hoạt động. Bạn có thể sử dụng Task.Run để di chuyển công việc liên quan tới CPU-bound để chạy luồng background, nhưng một luồng background không giúp được gì cho quy trình nó chỉ chờ kết quả có sẵn.
Cách tiếp cận bằng async đối với lập trình bất đồng bộ là nó được ưu tiên hơn những cách tiếp cận đã tồn tại trong hầu hết các trường hợp. Đặc biệt, cách tiếp cận này tốt hơn lớp BackgroundWorker cho hoạt động liên quan tới I/O bởi vì code đơn giản hơn và bạn không cần đề phòng các điều kiện khó khăn. Trong sự kết hợp với phương thức Task.Run, phương thức bất đồng bộ là tốt hơn BackgroundWorker đối với CPU-bound bởi vì lập trình bất đồng bộ tách chi tiết điều phối của code đang chạy của bạn từ công việc mà Task.Run chuyển đến nhóm luồng.
async và await
Nếu bạn xác định rằng một phương thức là một phương thức bất đồng bộ bằng việc sử dụng modifier async, bạn có hai khả năng sau:
-
Phương thức đánh dấu bất đồng bộ có thể sử dụng await để chỉ định cái điểm dừng lại để chờ. Toán tử await nói cho trình biên dịch là phương thức bất đồng bộ không thể tiếp tục qua thời điểm đó cho tới khi quá trình chờ bất đồng bộ hoàn thành. Trong khi chờ đợi, quyền điều khiển trả về trình gọi của phương thức bất đồng bộ.
Việc tạm dừng một phương thức bất đồng bộ ở một biểu thức await không tạo thành một lối thoát cho phương thức, và những khối cuối cùng không chạy
-
Phương thức đánh dấu bất đồng bộ có thể được chờ bởi phương thức gọi nó
Một phương thức bất đồng bộ điển hình bao gồm một hoặc nhiều lần xuất hiện của toán tử await, nhưng sự vắng mặt của biểu thức await không gây ra lỗi. Nếu một phương thức bất đồng bộ không sử dụng một toán tử để đánh dấu điểm chờ, phương thức thực thi như một phương thức đồng bộ, mặt dùng có modifier async. Trình biên dịch chỉ đưa ra cảnh báo cho các phương thức như vậy.
async và await là từ khóa theo ngữ cảnh. Để có thêm nhiều thông và ví dụ, xem thêm ở await và async nhé.
Kiểu trả về và tham số
Một phương thức bất đồng bộ thông thường trả về một Task
hoặc một Task<TResult>
. Trong phương thức bất đồng bộ, một toán tử await được áp dụng cho một task mà nó trả về từ một cuộc gọi đến một phương thức bất đồng bộ khác.
Bạn xác định Task<TResult>
như kiểu trả về nếu phương thức gồm một câu lệnh trả về xác định một toán hạng của kiểu TResult
.
Bạn sử dụng Task
như kiểu trả về nếu phương thức không có câu lệnh trả về hay có một câu lệnh trả về không trả về toán hạng.
Bắt đầu từ C# 7.0 bạn có thể xác định bất kỳ kiểu trả về, được cung cấp kiểu bao một phương thức GetAwaiter. ValueTask<TResult>
là một ví dụ của kiểu như vậy. Nó có sẵn ở Nuget package System.Threading.Tasks.Extension
Ví dụ dưới đây cho thấy bạn khai báo và gọi một phương thức mà trả về một Task<TResult>
hoặc một Task
:
Mỗi task trả về đại diện một đống thứ đang chạy. Một task đóng gói thông tin về trạng thái của quá trình bất đồng bộ và, cuối cùng, kết quả cuối cùng từ quá trình hoặc ngoại lệ mà quá trình đó raise lên nếu nó không thành công.
Một phương thức có thể có một kiểu là trả về void. Kiều này là được dùng chính để diễn tả xử lý sự kiện, khi một kiểu trả về void được yêu cầu. Xử lý sự kiện bất đồng bộ thường phục vụ như điểm bắt đầu cho chương trình bất đồng bộ.
Một phương thức bất đồng bộ có kiểu trả về void không thể được chờ, và chỗ gọi phương thức đó sẽ không thể bắt được bất kỳ ngoại lệ nào mà phương thức đó quăng ra.
Một phương thức bất đồng bộ không thể khai báo tham số in, ref, hoặc out, nhưng phương thức có thể gọi những phương thức như những tham số đó. Thông thường, một phương thức bất đồng bộ không thể trả về một giá trị tham chiếu, mặc dù nó có thể gọi phương thức với giá trị tham chiếu trả về.
Để có thêm thông tin và ví dụ, đọc Async return types (C#). Để có nhiều thông tin hơn về cách bắt ngoại lệ trong phương thức bất đồng bộ, xem try-catch
Quy ước đặt tên
Theo quy ước, phương thức mà kiểu chờ trả về phổ biến (ví dụ Task
, Task<T>
, ValueTask
, ValueTask<T>
) nên được kết thúc với "Async". Phương thức bắt đầu một hoạt động bất đồng bộ nhưng không trả về kiểu chờ không nên được đặt tên cuối cùng là "Async", nhưng có thể bắt đầu với "Begin" hoặc "Start", hoặc một vài đông từ khác để gợi ý phương thức này không trả về hoặc quăng kết quả của hoạt động.
Bạn có thể mặc kệ quy ước khi viết một sự kiện, lớp cơ bản hay một interface gợi ý một tên khác. Ví dụ, bạn không nên đổi tên các trình xử lý sự kiện phổ biến, chẳng hạn như OnButtonClick
Nhận xét
Đăng nhận xét