Trước tiên, cảm ơn bạn đã có những lời tốt đẹp. Nó thực sự là một tính năng tuyệt vời và tôi rất vui vì đã là một phần nhỏ của nó.
Nếu tất cả mã của tôi đang dần chuyển sang trạng thái không đồng bộ, tại sao không đặt tất cả mã không đồng bộ theo mặc định?
Chà, bạn đang phóng đại; tất cả mã của bạn không chuyển sang trạng thái không đồng bộ. Khi bạn cộng hai số nguyên "thuần túy" với nhau, bạn sẽ không phải chờ đợi kết quả. Khi bạn thêm hai số nguyên trong tương lai với nhau để có được số nguyên thứ ba trong tương lai - bởi vì đó Task<int>
là số nguyên mà bạn sẽ có quyền truy cập trong tương lai - tất nhiên bạn có thể sẽ chờ kết quả.
Lý do chính để không làm cho mọi thứ không đồng bộ là vì mục đích của async / await là giúp viết mã dễ dàng hơn trong một thế giới có nhiều thao tác có độ trễ cao . Phần lớn các hoạt động của bạn có độ trễ không cao, vì vậy sẽ không có ý nghĩa gì nếu lấy hiệu suất làm giảm độ trễ đó. Thay vào đó, một vài thao tác chính của bạn có độ trễ cao và những thao tác đó đang gây ra sự xâm nhập không đồng bộ của thây ma trong toàn bộ mã.
nếu hiệu suất là vấn đề duy nhất, chắc chắn một số tối ưu hóa thông minh có thể tự động loại bỏ chi phí khi không cần thiết.
Về lý thuyết, lý thuyết và thực hành tương tự nhau. Trong thực tế, chúng không bao giờ như vậy.
Hãy để tôi cung cấp cho bạn ba điểm chống lại kiểu chuyển đổi này, theo sau là vượt qua tối ưu hóa.
Điểm đầu tiên một lần nữa là: async trong C # / VB / F # về cơ bản là một dạng giới hạn của việc truyền tiếp tục . Một số lượng lớn các nghiên cứu trong cộng đồng ngôn ngữ chức năng đã đi vào việc tìm ra các cách để xác định cách tối ưu hóa mã sử dụng nhiều kiểu truyền tiếp tục. Nhóm biên dịch có thể sẽ phải giải quyết các vấn đề rất giống nhau trong một thế giới mà "async" là mặc định và các phương thức không phải async phải được xác định và khử async-ified. Nhóm C # không thực sự quan tâm đến việc giải quyết các vấn đề nghiên cứu mở, vì vậy đó là điểm lớn chống lại ngay ở đó.
Điểm thứ hai chống lại là C # không có mức độ "minh bạch tham chiếu" làm cho các loại tối ưu hóa này dễ kiểm soát hơn. Theo "tính minh bạch tham chiếu", tôi có nghĩa là thuộc tính mà giá trị của một biểu thức không phụ thuộc vào thời điểm nó được đánh giá . Các biểu thức như 2 + 2
là minh bạch về mặt tham chiếu; bạn có thể thực hiện đánh giá tại thời điểm biên dịch nếu bạn muốn, hoặc trì hoãn nó cho đến thời gian chạy và nhận được câu trả lời tương tự. Nhưng một biểu thức như x+y
không thể di chuyển theo thời gian vì x và y có thể thay đổi theo thời gian .
Không đồng bộ làm cho việc suy luận về thời điểm một tác dụng phụ sẽ khó khăn hơn nhiều. Trước khi async, nếu bạn nói:
M();
N();
và M()
đã void M() { Q(); R(); }
, và N()
đã void N() { S(); T(); }
, và R
và S
tạo ra tác dụng phụ, thì bạn biết rằng tác dụng phụ của R xảy ra trước tác dụng phụ của S. Nhưng nếu bạn có async void M() { await Q(); R(); }
thì đột nhiên điều đó đi ra ngoài cửa sổ. Bạn không có gì đảm bảo R()
sẽ xảy ra trước hay sau S()
(trừ khi tất nhiên M()
là đang chờ; nhưng tất nhiên Task
không cần phải đợi cho đến sau N()
.)
Bây giờ, hãy tưởng tượng rằng thuộc tính không còn biết thứ tự nào xảy ra tác dụng phụ áp dụng cho mọi đoạn mã trong chương trình của bạn ngoại trừ những đoạn mã mà trình tối ưu hóa quản lý để khử async-ify. Về cơ bản, bạn không còn manh mối nào nữa là các biểu thức sẽ được đánh giá theo thứ tự nào, có nghĩa là tất cả các biểu thức cần phải minh bạch về mặt tham chiếu, điều này rất khó trong một ngôn ngữ như C #.
Điểm thứ ba chống lại là bạn phải hỏi "tại sao async lại đặc biệt như vậy?" Nếu bạn định tranh luận rằng mọi hoạt động thực sự phải là một Task<T>
thì bạn cần phải trả lời được câu hỏi "tại sao không Lazy<T>
?" hoặc "tại sao không Nullable<T>
?" hoặc "tại sao không IEnumerable<T>
?" Bởi vì chúng tôi có thể dễ dàng làm điều đó. Tại sao không nên để mọi hoạt động được nâng lên thành nullable ? Hoặc mọi hoạt động được tính toán một cách lười biếng và kết quả được lưu vào bộ nhớ đệm để sử dụng sau này hoặc kết quả của mọi hoạt động là một chuỗi các giá trị thay vì chỉ một giá trị duy nhất . Sau đó, bạn phải cố gắng tối ưu hóa những tình huống mà bạn biết "ồ, điều này không bao giờ được để trống, vì vậy tôi có thể tạo mã tốt hơn", v.v.
Vấn đề là: tôi không rõ điều gì Task<T>
thực sự là đặc biệt để đảm bảo cho nhiều công việc này.
Nếu những thứ này bạn quan tâm thì tôi khuyên bạn nên điều tra các ngôn ngữ chức năng như Haskell, ngôn ngữ này có tính minh bạch tham chiếu mạnh hơn nhiều và cho phép tất cả các loại đánh giá không theo thứ tự và thực hiện bộ nhớ đệm tự động. Haskell cũng hỗ trợ mạnh mẽ hơn nhiều trong hệ thống loại hình của nó đối với các loại "sự sống đơn nguyên" mà tôi đã ám chỉ.