async & await - cuộc thăm dò cho các lựa chọn thay thế [đã đóng]


15

Bây giờ chúng tôi đã biết những gì đang có trong c # 5, rõ ràng vẫn còn một cơ hội cho chúng tôi ảnh hưởng đến việc lựa chọn hai từ khóa mới cho " Không đồng bộ " đã được công bố bởi Anders Heijsberg ngày hôm qua tại PDC10 .

async void ArchiveDocuments(List<Url> urls) {
    Task archive = null;
    for(int i = 0; i < urls.Count; ++i) {
        var document = await FetchAsync(urls[i]);
        if (archive != null)
            await archive;
        archive = ArchiveAsync(document);
    }
}

Eric Lippert có một lời giải thích về sự lựa chọn của hai từ khóa hiện tại và cách mà chúng đã bị hiểu sai trong các nghiên cứu về khả năng sử dụng. Các ý kiến ​​có một số đề xuất khác.

Xin vui lòng - một đề xuất cho mỗi câu trả lời, các bản sao sẽ bị thu hồi.


Theo cách "lập trình không đồng bộ tích hợp ngôn ngữ" mang lại cho chúng ta LIAP, không hoàn toàn lè lưỡi giống như LINQ;)
Stewol

1
Trừ khi bước nhảy vọt của nó.
Conrad Frix

3
Nhưng các từ viết tắt "Ngôn ngữ không đồng bộ tích hợp ngôn ngữ" lên độc đáo.
glenatron

Điều này đã được lạc đề.
DeadMG

Câu trả lời:


6

Cho rằng tôi không rõ về ý nghĩa / sự cần thiết của nó async, tôi thực sự không thể tranh luận với nó, nhưng gợi ý tốt nhất của tôi để thay thế awaitlà:

yield while (nhìn! không có từ khóa mới)

Lưu ý rằng đã suy nghĩ về điều này nhiều hơn một chút, tôi tự hỏi liệu sử dụng whilelại theo cách này có phải là một ý tưởng tốt hay không - xu hướng tự nhiên sẽ là mong đợi một boolean sau đó.

(Suy nghĩ: tìm từ khóa tốt cũng giống như tìm tên miền tốt :)


+1 và nhân tiện, bạn đánh bại tôi để bình luận về bài viết trên blog của anh ấy sau 7 phút ...
Lưu ý để tự - nghĩ về một cái tên

Nhưng bạn không nhất thiết phải thực hiện nếu nhiệm vụ đã hoàn thành. Nhưng bạn luôn chờ đợi hoàn thành nhiệm vụ (mặc dù không bao giờ chờ đợi).
Allon Guralnek

@ ALLon Bạn không nhất thiết phải chạy phần thân của while(x) {...}một trong hai, nếu xlà sai.
Lưu ý đến bản thân - nghĩ về một cái tên

@ Chú ý: Vâng không có động từ trong while. Nếu bạn thêm một động từ, như do, thì bạn nhận được do {...} while (x), nó thực thi phần thân bất kể x (ít nhất một lần). Gợi ý của bạn yield whilecó vẻ rất giống với do while, nhưng với sự đảm bảo ngược lại khi thực hiện động từ, có thể hơi sai lệch (nhưng không phải là vấn đề lớn). Điều tôi không thích nhất yieldlà nó ngụ ý việc thực hiện một cơ chế. Toàn bộ điểm của async/ awaitlà bạn viết một hoạt động không đồng bộ theo kiểu đồng bộ. yieldphá vỡ phong cách đồng bộ đó.
Allon Guralnek

Là một từ khóa mới nhất thiết phải là một điều xấu? Theo tôi hiểu, awaittừ khóa sẽ được nhận dạng theo ngữ cảnh, vì vậy bạn vẫn có thể có một phương thức hoặc biến có tên là "await" nếu bạn muốn. Ở một mức độ nào đó, tôi nghĩ rằng việc sử dụng một từ khóa mới cho chức năng mới ít gây nhầm lẫn hơn là sử dụng lại một từ khóa hiện tại có nghĩa là nhiều hơn một điều. (ví dụ phóng đại: dangermouse.net/esoteric/ook.html )
Tim Goodman

5

Làm thế nào về việc không có một từ khóa?

Tôi muốn trình biên dịch nhận ra rằng, hầu hết thời gian, khi tôi gọi một phương thức không đồng bộ, tôi muốn kết quả của nó.

Document doc = DownloadDocumentAsync();

Đó là nó. Lý do mọi người gặp khó khăn khi nghĩ ra một từ khóa cho việc này là vì nó giống như có một từ khóa để "làm điều bạn làm nếu mọi thứ hoàn toàn bình thường". Đó phải là mặc định, không yêu cầu một từ khóa.

Cập nhật

Ban đầu tôi đề nghị trình biên dịch nên thông minh với suy luận kiểu để tìm ra phải làm gì. Nghĩ thêm về điều này, tôi vẫn giữ cách triển khai hiện có trong CTP, nhưng hãy thực hiện một vài bổ sung nhỏ cho nó, để giảm bớt các trường hợp bạn cần sử dụng awaittừ khóa một cách rõ ràng.

Chúng tôi phát minh ra một thuộc tính : [AutoAwait]. Điều này chỉ có thể được áp dụng cho các phương pháp. Một cách để áp dụng điều này vào phương pháp của bạn là đánh dấu nó async. Nhưng bạn cũng có thể làm điều đó bằng tay, ví dụ:

[AutoAwait]
public Task<Document> DownloadDocumentAsync()

Sau đó, bên trong bất kỳ asyncphương thức nào , trình biên dịch sẽ cho rằng bạn muốn chờ cuộc gọi đến DownloadDocumentAsync, vì vậy bạn không cần chỉ định nó. Bất kỳ cuộc gọi đến phương thức đó sẽ tự động chờ đợi nó.

Document doc = DownloadDocumentAsync();

Bây giờ, nếu bạn muốn "thông minh" và có được Task<Document>, bạn sử dụng một toán tử start, chỉ có thể xuất hiện trước một cuộc gọi phương thức:

Task<Document> task = start DownloadDocumentAsync();

Tôi gọn gàng. Bây giờ một cuộc gọi phương thức đơn giản có nghĩa là những gì nó thường có nghĩa là: chờ cho phương thức hoàn thành. Và startchỉ ra điều gì đó khác biệt: đừng chờ đợi.

Đối với mã xuất hiện bên ngoài một asyncphương thức, cách duy nhất bạn được phép gọi một [AutoAwait]phương thức là bằng cách thêm tiền tố vào đó start. Điều này buộc bạn phải viết mã có cùng ý nghĩa bất kể nó có xuất hiện trong một asyncphương thức hay không.

Rồi tôi bắt đầu tham lam! :)

Đầu tiên, tôi muốn asyncáp dụng các phương thức giao diện:

interface IThing
{
    async int GetCount();
} 

Về cơ bản, điều đó có nghĩa là phương thức thực hiện phải trả về Task<int>hoặc một cái gì đó tương thích awaitvà người gọi phương thức sẽ có [AutoAwait]hành vi.

Ngoài ra khi tôi thực hiện phương pháp trên, tôi muốn có thể viết:

async int GetCount()

Vì vậy, tôi không phải đề cập đến Task<int>như là loại trả lại.

Ngoài ra, tôi muốn asyncáp dụng cho các loại đại biểu (mà suy cho cùng, giống như các giao diện với một phương thức). Vì thế:

public async delegate TResult AsyncFunc<TResult>();

Một asyncđại biểu đã - bạn đoán nó - [AutoAwait]hành vi. Từ một asyncphương thức bạn có thể gọi nó và nó sẽ tự động được chỉnh sửa await(trừ khi bạn chọn chỉ startnó). Và nếu bạn nói:

AsyncFunc<Document> getDoc = DownloadDocumentAsync;

Điều đó chỉ hoạt động. Đây không phải là một cuộc gọi phương thức. Chưa có nhiệm vụ nào được bắt đầu - async delegateđây không phải là một nhiệm vụ. Đó là một nhà máy để thực hiện các nhiệm vụ. Bạn có thể nói:

Document doc = getDoc();

Và điều đó sẽ bắt đầu một nhiệm vụ và chờ đợi nó kết thúc và cung cấp cho bạn kết quả. Hoặc bạn có thể nói:

Task<Document> t = start getDoc();

Vì vậy, một nơi trong đó "đường ống dẫn nước" bị rò rỉ là nếu bạn muốn tạo một đại biểu cho một asyncphương thức, bạn phải biết sử dụng một async delegateloại. Vì vậy, thay vì Funcbạn phải nói AsyncFunc, và như vậy. Mặc dù một ngày nào đó loại điều đó có thể được sửa chữa bằng suy luận loại được cải thiện.

Một câu hỏi khác là điều gì sẽ xảy ra nếu bạn nói bắt đầu với một phương thức thông thường (không đồng bộ). Rõ ràng một lỗi biên dịch sẽ là lựa chọn an toàn. Nhưng có những khả năng khác.


Điều này có thể thực hiện được với một chuyển đổi ngầm định nhưng nếu không sẽ yêu cầu đánh giá từ trái sang phải của câu lệnh (điều này hoàn toàn ngược lại với cách trình biên dịch thường hoạt động, ngoại trừ lambdas). Tôi nghĩ rằng tôi vẫn chống lại điều này bởi vì nó ngăn chặn việc sử dụng var, có khả năng phải thay thế một số loại tên rõ ràng dài và cũng mơ hồ giữa awaittrường hợp và trường hợp ai đó vô tình gọi phương thức async thay vì phương thức đồng bộ thông thường. Nó có vẻ trực quan lúc đầu nhưng thực sự vi phạm nguyên tắc ít bất ngờ nhất.
Aaronaught

@Aaronaught - Tại sao nó ngăn chặn việc sử dụng var? Tôi tự hỏi nếu bạn đang trả lời bản sửa đổi trước đây của câu trả lời của tôi ... Tôi đã viết lại hoàn toàn. Bây giờ bạn có thể nghĩ về đề xuất này như sau: nếu phương thức được đánh dấu bằng một thuộc tính đặc biệt, thì như thể awaittừ khóa sẽ tự động được chèn trước các lệnh gọi đến phương thức đó (trừ khi bạn loại bỏ điều này bằng starttiền tố). Mọi thứ vẫn chính xác như trong CTP, và do đó varhoạt động tốt.
Daniel Earwicker

Quả thực tôi rất ... lạ khi tôi quyết định xem lại chủ đề này và trả lời câu trả lời của bạn gần như chính xác cùng lúc bạn quyết định chỉnh sửa nó. Tôi sẽ phải đọc lại ngay bây giờ ...
Aaronaught

1
Tôi thích sự đảo ngược của bạn về từ khóa đang chờ. Và tôi cũng không thích sự dư thừa của ba public async Task<int> FooAsync().
Allon Guralnek

1
Vâng, tôi thấy rằng quy ước đặt tên Async-postfix như một dấu hiệu cho thấy một cái gì đó có thể được nắm bắt chính thức hơn. Về cơ bản, nếu có một quy tắc nói rằng "các phương thức như thế này nên được đặt tên theo một cách nhất định, vì vậy mọi người biết cách gọi chúng đúng" thì theo quy tắc đó có thể được sử dụng để quy các phương thức đó theo một cách nhất định, và sau đó trình biên dịch giúp bạn gọi họ đúng cách.
Daniel Earwicker


4

Tôi nghĩ asynclà tốt, nhưng có lẽ đó là vì tôi liên kết nó với các trang ASP.NET async - cùng một ý tưởng.

Đối với awaittừ khóa tôi thích continue afterhoặc resume after.

Tôi không thích yieldhoặc bất kỳ biến thể nào của nó, bởi vì ngữ nghĩa là phương pháp có thể không bao giờ thực sự mang lại sự thực thi; nó phụ thuộc vào trạng thái của nhiệm vụ.


Tôi thích resume aftercho await. Có lẽ asynccó thể được gọi resumable.
Tim Goodman

Mặc dù tôi chỉ thích after, continue aftercách tiếp cận này có một lợi thế triển khai mạnh mẽ: nó bao gồm một từ khóa theo ngữ cảnh hiện có, nhưng với một cú pháp không tương thích với cách sử dụng hiện tại. Điều đó đảm bảo rằng việc bổ sung sẽ không bao giờ phá vỡ mã hiện có. Khi sử dụng một từ khóa hoàn toàn mới, việc triển khai cần phải đối phó với việc sử dụng từ có thể như một định danh trên mã cũ hơn, điều này có thể trở nên khá khó khăn.
Edurne Pascual

3

Tôi cũng đã thêm vào nhận xét trên blog của Eric, tôi không thấy bất kỳ vấn đề nào khi sử dụng cùng một từ khóa async

var data = async DownloadFileAsync(url);

Tôi chỉ bày tỏ rằng tôi muốn tải xuống tệp không đồng bộ. Có một chút dư thừa ở đây, "async" xuất hiện hai lần, vì nó cũng có trong tên phương thức. Trình biên dịch có thể cực kỳ thông minh và phát hiện quy ước rằng các phương thức kết thúc bằng "Async" là các phương thức async nguyên vẹn và thêm nó cho chúng tôi trong mã được biên dịch. Vì vậy, thay vào đó bạn chỉ muốn gọi

var data = async DownloadFile(url);

trái ngược với cách gọi đồng bộ

var data = DownloadFile(url);

Heck, chúng ta cũng có thể định nghĩa chúng theo cùng một cách, vì từ khóa async có trong khai báo của chúng ta, tại sao chúng ta phải thêm "Async" vào mỗi tên phương thức - trình biên dịch có thể làm điều đó cho chúng ta.


Tôi thích đường bạn đã thêm, mặc dù các anh chàng C # có thể sẽ không dùng nó. (FWIW, họ đã làm một cái gì đó tương tự khi tìm kiếm tên thuộc tính)
Lưu ý để tự nghĩ về một cái tên

2
Và cho rằng asynctừ khóa trên phương thức này chỉ là một từ hay (nếu tôi hiểu đúng), tôi tự hỏi liệu điều tốt nhất sẽ không làm ngược lại với những gì bạn đề xuất: từ bỏ asyncphương pháp và chỉ sử dụng nó nơi họ hiện đang có await.
Stewol

Đó là một khả năng. Từ khóa async trên phương thức delcaration chỉ ở đó cho sự sạch sẽ thực sự. Tôi muốn nó được giữ ở đó, nhưng không có yêu cầu cho chúng tôi thêm "Async" vào tên phương thức. Ví dụ async Task<Byte[]> DownloadFile(...)chứ không phải Task<Byte[]> DownloadFileAsync(...)(Cái sau sẽ là chữ ký được biên dịch). Cả hai cách đều hoạt động.
Đánh dấu H

Tôi thực sự không phải là một fan hâm mộ của điều này. Như trong một bình luận trước đây tôi phải chỉ ra rằng phiên bản cuối cùng của bạn vi phạm nguyên tắc ít gây bất ngờ nhất, vì nó thực sự đang gọi một phương thức hoàn toàn khác với cách viết, và việc thực hiện và chữ ký của phương thức đó hoàn toàn phụ thuộc vào lớp thực hiện . Ngay cả phiên bản đầu tiên cũng có vấn đề vì nó không thực sự nói lên điều gì (thực thi không đồng bộ phương thức không đồng bộ này?). Ý nghĩ chúng tôi đang cố gắng thể hiện là một sự thực thi tiếp tục hoặc bị trì hoãn và điều này hoàn toàn không thể hiện điều đó.
Aaronaught

3

async = task - Đó là sửa đổi một hàm để trả về một tác vụ, vậy tại sao không sử dụng từ khóa "task"?

await = finish - Chúng ta không nhất thiết phải chờ, nhưng nhiệm vụ phải "kết thúc" trước khi sử dụng kết quả.


Thật sự rất khó để tranh luận với sự đơn giản ở đây.
sblom

2

Tôi thích yield until. yield while, đã được đề xuất, là tuyệt vời và không giới thiệu bất kỳ từ khóa mới nào, nhưng tôi nghĩ "cho đến khi" nắm bắt hành vi tốt hơn một chút.

Tôi nghĩ yield <something>là một ý tưởng tuyệt vời, bởi vì năng suất đã nắm bắt được ý tưởng làm cho phần còn lại của phương pháp tiếp tục rất tốt. Có lẽ ai đó có thể nghĩ về một từ tốt hơn "cho đến khi."


2

Tôi chỉ muốn đăng ký phiếu bầu của mình cho đề xuất của Aaron G comefrom- cách sử dụng phù hợp đầu tiên tôi từng thấy trong tuyên bố COMEFROM của INTERCAL . Ý tưởng là nó trái ngược với GOTO (nhảy ra khỏi câu lệnh GOTO) ở chỗ nó làm cho một số vị trí trong mã của bạn chuyển sang câu lệnh COMEFROM.


2

Vì chúng ta đang làm việc với Task<T>s, nên sử dụng startnhư một từ khóa trước câu lệnh, như trong:

start var document = FetchAsync(urls[i]);


Hmmm, có lẽ finishsẽ còn tốt hơn start?
Nhân vật chính

1

Điều đáng chú ý là F # cũng sử dụng asynctừ khóa trong quy trình làm việc không đồng bộ của nó, điều này khá giống với chức năng async mới trong C # 5. Vì vậy, tôi sẽ giữ một cái giống nhau

Đối với awaittừ khóa trong F #, họ chỉ sử dụng let!thay vì let. C # không có cú pháp gán giống nhau, vì vậy họ cần một cái gì đó ở phía bên phải của =dấu hiệu. Như Stewol đã nói, nó hoạt động giống như yieldvậy nên nó gần như là một biến thể của điều đó.


1
Không cần phải "chờ đợi" trong một nhiệm vụ (mặc dù tất nhiên là như vậy.) Đó là hợp pháp với tư cách là một nhà điều hành trên hầu hết mọi biểu thức có loại mà chúng ta có thể tìm thấy trên GetAwaiter. (Các quy tắc chính xác vẫn chưa được áp dụng thành một hình thức có thể xuất bản.)
Eric Lippert

1
@Eric, trong F # sẽ như vậy do!, nhưng bạn biết rằng ...
Stewol

1

yield async FetchAsync(..)


Điều này hoàn toàn phù hợp với công cụ asyncsửa đổi mà bạn cần đưa vào phương thức bạn đang gọi. Và cũng là ngữ nghĩa của hiện tại yield return, bạn trả về mang lại sự thực thi cho mã liệt kê trong khi trong trường hợp này bạn đang mang lại sự thực thi của mình cho phương thức không đồng bộ.

Hãy tưởng tượng nếu trong tương lai sẽ có những cách sử dụng khác yield, chúng ta có thể thêm một yield xtrong đó x là tính năng mới sáng bóng thay vì có tất cả các từ khóa khác nhau này để thực hiện hầu hết cùng một việc, mang lại hiệu quả thực thi.

Thành thật mà nói, tôi hoàn toàn không hiểu lập luận 'không mang lại kết quả thực thi'. Rốt cuộc, không phải điểm gọi phương thức khác đã là 'thực hiện năng suất' cho phương thức đó sao? Bất kể nó không đồng bộ hay không? Tôi đang thiếu một cái gì đó ở đây?

Và tốt cho bạn nếu asynctrả về đồng bộ nhưng có từ khóa ở đó sẽ biểu thị rằng có khả năng phương thức sẽ chạy không đồng bộ và bạn sẽ mang lại sự thực thi cho phương thức khác. Phương thức của bạn nên tính đến điều đó bất kể phương thức đó có thực sự thực hiện các cuộc gọi không đồng bộ hay không.

IMO Tôi nghĩ rằng các trường hợp 'không mang lại lợi nhuận' khác nhau là một chi tiết thực hiện. Tôi muốn bảo đảm tính nhất quán trong ngôn ngữ (nghĩa là sử dụng lại yield).


0

Còn về complete"Tôi muốn nhiệm vụ hoàn thành" thì sao?

Task<byte[]> downloadTask = DownloadFileAsync(url);
byte[] data = complete downloadTask;

1
Tại sao các downvote? Đó là phép lịch sự ít nhất là giải thích chính bạn sau khi hạ cấp.
Allon Guralnek

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.