Từ khóa Async await có tương đương với ContinueWith lambda không?


81

Ai đó có thể vui lòng đủ tốt để xác nhận xem tôi đã hiểu đúng từ khóa Async await chưa? (Sử dụng phiên bản 3 của CTP)

Cho đến nay, tôi đã phát hiện ra rằng việc chèn từ khóa await trước một cuộc gọi phương thức về cơ bản thực hiện 2 điều, A. Nó tạo ra một trả về ngay lập tức và B. Nó tạo ra một "tiếp tục" được gọi sau khi hoàn thành việc gọi phương thức async. Trong mọi trường hợp, phần tiếp tục là phần còn lại của khối mã cho phương thức.

Vì vậy, những gì tôi đang tự hỏi là, hai bit mã này có tương đương về mặt kỹ thuật không, và nếu có, thì điều này về cơ bản có nghĩa là từ khóa await giống hệt với việc tạo ContinueWith Lambda (nghĩa là: về cơ bản nó là một phím tắt trình biên dịch cho một)? Nếu không, sự khác biệt là gì?

bool Success =
    await new POP3Connector(
        "mail.server.com", txtUsername.Text, txtPassword.Text).Connect();
// At this point the method will return and following code will
// only be invoked when the operation is complete(?)
MessageBox.Show(Success ? "Logged In" : "Wrong password");

VS

(new POP3Connector(
    "mail.server.com", txtUsername.Text, txtPassword.Text ).Connect())
.ContinueWith((success) =>
    MessageBox.Show(success.Result ? "Logged In" : "Wrong password"));

Câu trả lời:


83

Ý tưởng chung là đúng - phần còn lại của phương pháp được tạo thành phần tiếp theo của các loại.

Bài đăng trên blog "đường dẫn nhanh" có chi tiết về cách hoạt động của chuyển đổi async/ awaittrình biên dịch.

Sự khác biệt, ngoài đầu tôi:

Các awaittừ khóa cũng làm cho việc sử dụng một "bối cảnh lịch" khái niệm. Bối cảnh lập kế hoạch là SynchronizationContext.Currentnếu nó tồn tại, sẽ trở lại TaskScheduler.Current. Sự tiếp tục sau đó được chạy trên bối cảnh lập lịch trình. Vì vậy, một ước tính gần hơn sẽ được chuyển TaskScheduler.FromCurrentSynchronizationContextvào ContinueWith, quay trở lại TaskScheduler.Currentnếu cần thiết.

Thực tế async/ awaittriển khai dựa trên đối sánh mẫu; nó sử dụng một mẫu "có thể chờ đợi" cho phép những thứ khác ngoài nhiệm vụ được chờ đợi. Một số ví dụ là các API không đồng bộ của WinRT, một số phương thức đặc biệt như các phương thức Yieldquan sát Rx và các cổng chờ đặc biệt không ảnh hưởng nhiều đến GC . Nhiệm vụ rất mạnh mẽ, nhưng chúng không phải là thứ duy nhất được chờ đợi.

Một sự khác biệt nhỏ nữa xuất hiện trong tâm trí: nếu phương thức chờ đã được hoàn thành, thì asyncphương thức sẽ không thực sự trả về tại thời điểm đó; nó tiếp tục một cách đồng bộ. Vì vậy, nó giống như vượt qua TaskContinuationOptions.ExecuteSynchronously, nhưng không có các vấn đề liên quan đến ngăn xếp.


2
rất hay đã nói - Tôi cố gắng trì hoãn các bài đăng của Jon vì chúng rộng hơn nhiều so với bất cứ điều gì mà tôi có thể đưa vào câu trả lời trên SO, nhưng Stephen hoàn toàn đúng. WRT gì awaitable (và GetAwaiter nói riêng), bài # 3 của ông là rất hữu ích IMHO :) msmvps.com/blogs/jon_skeet/archive/2011/05/13/...
James Manning

4
Stephen's chỗ ở đây. Đối với các ví dụ đơn giản, thật dễ dàng để nghĩ rằng async / await chỉ là một phím tắt cho ContinueWith - tuy nhiên, tôi muốn nghĩ ngược lại. Async / await thực sự là một biểu hiện mạnh mẽ hơn cho những gì bạn đã sử dụng ContinueWith cho. Vấn đề là ContinueWith (...) sử dụng lambdas và cho phép việc thực thi được chuyển sang phần tiếp tục, nhưng các khái niệm luồng điều khiển khác như vòng lặp là khá bất khả thi nếu bạn phải đặt một nửa phần thân của vòng lặp trước ContinueWith (.. .) và nửa còn lại sau khi. Bạn kết thúc với chuỗi tiếp tục thủ công.
Theo Yaung

7
Một ví dụ khác trong đó async / await biểu cảm hơn nhiều so với ContinueWith (...) là dòng ngoại lệ. Bạn có thể chờ đợi nhiều lần trong cùng một khối try và đối với mỗi giai đoạn thực thi, các ngoại lệ của chúng có thể được chuyển sang cùng một khối catch (...) mà không cần phải viết hàng tấn mã thực hiện điều đó một cách rõ ràng.
Theo Yaung

2
Phần cuối cùng của async / await đáng chú ý là đó là "khái niệm cấp cao hơn" trong khi ContinueWith (...) thủ công hơn và có lambdas, sáng tạo ủy quyền, v.v. Với các khái niệm cấp cao hơn, có nhiều cơ hội hơn để tối ưu hóa - vì vậy, ví dụ, nhiều lượt chờ trong cùng một phương thức thực sự "chia sẻ" cùng một bao đóng lambda (nó giống như chi phí của một lambda), trong khi ContinueWith (...) nhận được chi phí mỗi khi bạn gọi nó, bởi vì bạn đã viết một lambda rõ ràng, vì vậy trình biên dịch cung cấp cho bạn.
Theo Yaung

1
@MobyDisk: Để làm rõ, awaitvẫn chụp SynchronizationContext.Currentnhư mọi khi. Nhưng trên ASP.NET Core, SynchronizationContext.Currentnull.
Stephen Cleary

8

Về cơ bản nó là như vậy, nhưng mã được tạo ra không chỉ như vậy. Để biết thêm chi tiết về mã được tạo, tôi thực sự giới thiệu loạt bài Eduasync của Jon Skeet:

http://codeblog.jonskeet.uk/category/eduasync/

Đặc biệt, bài đăng số 7 nói về những gì được tạo (kể từ CTP 2) và lý do, vì vậy có lẽ rất phù hợp với những gì bạn đang tìm kiếm vào lúc này:

http://codeblog.jonskeet.uk/2011/05/20/eduasync-part-7-generated-code-from-a-simple-async-method/

CHỈNH SỬA: Tôi nghĩ rằng nó có khả năng chi tiết hơn những gì bạn đang tìm kiếm từ câu hỏi, nhưng nếu bạn đang tự hỏi mọi thứ trông như thế nào khi bạn có nhiều lần chờ đợi trong phương pháp, điều đó sẽ được đề cập trong bài đăng số 9 :)

http://codeblog.jonskeet.uk/2011/05/30/eduasync-part-9-generated-code-for-multiple-awaits/

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.