Tại sao chúng ta cần từ khóa async?


19

Tôi mới bắt đầu chơi xung quanh với async / await trong .Net 4.5. Một điều ban đầu tôi tò mò, tại sao từ khóa async lại cần thiết? Lời giải thích tôi đọc là nó là một điểm đánh dấu để trình biên dịch biết một phương thức đang chờ một cái gì đó. Nhưng có vẻ như trình biên dịch sẽ có thể tìm ra điều này mà không cần một từ khóa. Vì vậy, nó làm gì khác?


2
Đây là một bài viết blog (loạt) thực sự hay đi sâu vào một số chi tiết mô tả từ khóa trong C # và chức năng liên quan trong F #.
paul

Tôi nghĩ rằng nó chủ yếu là một điểm đánh dấu cho nhà phát triển, chứ không phải cho trình biên dịch.
CodeInChaos

8
Bài viết này giải thích nó: blogs.msdn.com/b/ericlippert/archive/2010/11/11/...
svick

@svick cảm ơn, đây là những gì tôi đang tìm kiếm. Làm cho cảm giác hoàn hảo bây giờ.
Điều

Câu trả lời:


23

Có một số câu trả lời ở đây và tất cả chúng đều nói về những gì phương thức async làm, nhưng không ai trong số chúng trả lời câu hỏi, đó là lý do asynccần thiết như một từ khóa đi trong khai báo hàm.

Đó không phải là "chỉ đạo trình biên dịch biến đổi hàm theo cách đặc biệt"; awaitmột mình có thể làm điều đó. Tại sao? Bởi vì C # đã có một cơ chế khác trong đó sự hiện diện của một từ khóa đặc biệt trong thân phương thức làm cho trình biên dịch thực hiện các async/awaitphép biến đổi cực (và rất giống với ) trên thân phương thức : yield.

Ngoại trừ đó yieldkhông phải là từ khóa riêng của nó trong C # và hiểu lý do tại sao cũng sẽ giải thích async. Không giống như trong hầu hết các ngôn ngữ hỗ trợ cơ chế này, trong C # bạn không thể nói yield value;Bạn phải nói yield return value;thay thế. Tại sao? Bởi vì nó đã được thêm vào ngôn ngữ sau khi C # đã tồn tại và khá hợp lý khi cho rằng ai đó, ở đâu đó, có thể đã sử dụng yieldlàm tên của một biến. Nhưng vì không có kịch bản tồn tại trước đó <variable name> returnvề mặt cú pháp, yield returnnên đã được thêm vào ngôn ngữ để có thể giới thiệu các trình tạo trong khi duy trì khả năng tương thích ngược 100% với mã hiện có.

Và đây là lý do tại sao asyncđược thêm vào như một công cụ sửa đổi chức năng: để tránh phá vỡ mã hiện có được sử dụng awaitlàm tên biến. Vì không có asyncphương thức nào tồn tại, không có mã cũ nào bị vô hiệu hóa và trong mã mới, trình biên dịch có thể sử dụng sự hiện diện của asyncthẻ để biết rằng awaitnên được coi là một từ khóa và không phải là một định danh.


3
Đây là khá nhiều những gì bài viết này nói quá (ban đầu được đăng bởi @svick trong các bình luận), nhưng không ai từng đăng nó như một câu trả lời. Chỉ mất hai năm rưỡi! Cảm ơn :)
ConditionRacer

Điều đó không có nghĩa là trong một số phiên bản trong tương lai, yêu cầu chỉ định asynctừ khóa có thể bị loại bỏ? Rõ ràng đó sẽ là một sự phá vỡ BC nhưng nó có thể được cho là ảnh hưởng đến rất ít dự án và là một yêu cầu đơn giản để nâng cấp (tức là thay đổi một tên biến).
Câu hỏi Quolonel

6

nó thay đổi phương thức từ một phương thức bình thường thành một đối tượng với hàm gọi lại đòi hỏi một cách tiếp cận hoàn toàn khác để tạo mã

và khi một cái gì đó quyết liệt như thế xảy ra, theo thông lệ, nó sẽ biểu thị nó rõ ràng (chúng tôi đã học được bài học đó từ C ++)


Vì vậy, đây thực sự là một cái gì đó cho người đọc / lập trình viên, và không quá nhiều cho trình biên dịch?
ConditionRacer

@ Justin984 nó chắc chắn sẽ giúp báo hiệu cho trình biên dịch một cái gì đó đặc biệt cần phải xảy ra
ratchet freak

Tôi đoán tôi tò mò tại sao trình biên dịch cần nó. Không thể chỉ phân tích phương thức và thấy rằng sự chờ đợi đang ở đâu đó trong thân phương thức?
ConditionRacer

nó giúp ích khi bạn muốn lên lịch nhiều asyncs cùng một lúc để chúng không chạy lần lượt nhưng đồng thời (nếu chủ đề có sẵn ít nhất)
ratchet freak

@ Justin984: Không phải mọi phương thức trả về Task<T>thực sự có các đặc điểm của một asyncphương thức - ví dụ: bạn có thể chỉ cần chạy qua một số logic nghiệp vụ / nhà máy và sau đó ủy quyền cho một số phương thức khác trả về Task<T>. asynccác phương thức có kiểu trả về Task<T>trong chữ ký nhưng không thực sự trả về a Task<T>, chúng chỉ trả về a T. Để tìm ra tất cả những điều này mà không có asynctừ khóa, trình biên dịch C # sẽ phải thực hiện tất cả các loại kiểm tra sâu về phương thức, điều này có thể sẽ làm chậm nó đi một chút và dẫn đến mọi cách thức mơ hồ.
Aaronaught

4

Toàn bộ ý tưởng với các từ khóa như "async" hoặc "không an toàn" là loại bỏ sự mơ hồ về cách xử lý mã mà họ sửa đổi. Trong trường hợp từ khóa async, nó báo cho trình biên dịch coi phương thức được sửa đổi là thứ không cần trả về ngay lập tức. Điều này cho phép luồng trong đó phương thức này được sử dụng để tiếp tục mà không phải chờ kết quả của phương thức đó. Đó thực sự là một tối ưu hóa mã.


Tôi bị cám dỗ để downvote bởi vì trong khi đoạn đầu tiên là chính xác, việc so sánh với các ngắt là không chính xác (theo ý kiến ​​được thông báo của tôi).
paul

Tôi thấy chúng có phần giống nhau về mục đích nhưng tôi sẽ loại bỏ nó vì mục đích rõ ràng.
Kỹ sư thế giới

Các ngắt giống như các sự kiện hơn là mã async trong tâm trí của tôi - chúng gây ra chuyển đổi ngữ cảnh và chạy đến hoàn thành trước khi mã bình thường được nối lại (và mã thông thường cũng có thể hoàn toàn không biết gì về nó đang chạy), trong khi với mã async, phương thức có thể chưa hoàn thành trước khi chuỗi cuộc gọi được nối lại. Tôi thấy những gì bạn đã cố gắng để nói các cơ chế khác nhau đòi hỏi phải thực hiện khác nhau dưới mui xe, nhưng các ngắt chỉ không hợp tác với tôi để minh họa điểm này. (+1 cho phần còn lại, btw.)
paul

0

OK, đây là của tôi về nó.

Có một thứ gọi là coroutines đã được biết đến trong nhiều thập kỷ. ( "Knuth và Hopper" đẳng cấp "trong nhiều thập kỷ") Họ là những khái quát của chương trình con , trong đó không chỉ làm họ nhận được và kiểm soát phát hành tại chức năng bắt đầu và tuyên bố trở lại, nhưng họ cũng làm điều đó tại các điểm cụ thể ( điểm treo ). Một chương trình con là một coroutine không có điểm treo.

Chúng có thể DỄ DÀNG để thực hiện với các macro C, như được trình bày trong bài báo sau về "protothreads". ( http://dunkels.com/adam/dunkels06protothreads.pdf ) Đọc nó. Tôi sẽ đợi...

Điểm mấu chốt của điều này là các macro tạo ra switchmột casenhãn lớn và nhãn ở mỗi điểm treo. Tại mỗi điểm treo, hàm sẽ lưu giá trị của casenhãn ngay sau đó để nó biết nơi tiếp tục thực hiện lần sau khi được gọi. Và nó trả lại quyền kiểm soát cho người gọi.

Điều này được thực hiện mà không sửa đổi luồng kiểm soát rõ ràng của mã được mô tả trong "protothread".

Bây giờ hãy tưởng tượng rằng bạn có một vòng lặp lớn gọi lần lượt tất cả các "protothreads" này và bạn sẽ thực hiện đồng thời "protothreads" trên một chuỗi.

Cách tiếp cận này có hai nhược điểm:

  1. Bạn không thể giữ trạng thái trong các biến cục bộ giữa các lần thay đổi.
  2. Bạn không thể tạm dừng "protothread" từ độ sâu cuộc gọi tùy ý. (tất cả các điểm treo phải ở mức 0)

Có cách giải quyết cho cả hai:

  1. Tất cả các biến cục bộ phải được kéo lên đến bối cảnh của protothread (bối cảnh vốn đã cần thiết bởi thực tế protothread phải lưu trữ điểm nối lại tiếp theo của nó)
  2. Nếu bạn cảm thấy bạn thực sự cần phải gọi một protothread khác từ một protothread, hãy "sinh ra" một protothread trẻ em và đình chỉ cho đến khi hoàn thành đứa trẻ.

Và nếu bạn có hỗ trợ trình biên dịch để thực hiện công việc viết lại mà các macro và cách khắc phục, bạn có thể viết mã protothread như bạn dự định và chèn các điểm treo với một từ khóa.

Và đây là những gì asyncawaittất cả là về: tạo ra các coroutines (stackless).

Các coroutines trong C # được hợp nhất thành các đối tượng của lớp (chung hoặc không chung) Task.

Tôi thấy những từ khóa này rất sai lệch. Đọc tinh thần của tôi là:

  • async là "đáng ngờ"
  • await là "đình chỉ cho đến khi hoàn thành"
  • Task là "tương lai"

Hiện nay. Chúng ta có thực sự cần phải đánh dấu chức năng async? Ngoài việc nói rằng nó sẽ kích hoạt các cơ chế viết lại mã để làm cho hàm trở thành một coroutine, nó giải quyết một số sự mơ hồ. Hãy xem xét mã này.

public Task<object> AmIACoroutine() {
    var tcs = new TaskCompletionSource<object>();
    return tcs.Task;
}

Giả sử đó asynclà không bắt buộc, đây là một coroutine hoặc một chức năng bình thường? Trình biên dịch có nên viết lại nó như một coroutine hay không? Cả hai có thể có thể với ngữ nghĩa cuối cùng khác nhau.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.