async i czekaj - ankieta w sprawie alternatyw [zamknięte]


15

Teraz, gdy wiemy, co czeka nas na c # 5, najwyraźniej nadal mamy możliwość wpływania na wybór dwóch nowych słów kluczowych dla „ Asynchronii ”, ogłoszonych wczoraj przez Andersa Heijsberga na 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 wyjaśnił wybór dwóch aktualnych słów kluczowych oraz sposób, w jaki zostały one źle zrozumiane w badaniach użyteczności. Komentarze mają kilka innych propozycji.

Proszę - jedna sugestia na odpowiedź, duplikaty zostaną zniszczone.


Nawiasem mówiąc, „zintegrowane z językiem programowanie asynchroniczne” daje nam LIAP, nie całkiem zjeżdża z języka w taki sam sposób jak LINQ;)
Benjol,

1
Chyba że jest to wyraźny skok.
Conrad Frix

3
Ale akronimy „zintegrowane językowo środowisko wykonawcze asynchroniczne” ładnie się poprawia.
glenatron

To musi być nie na temat.
DeadMG

Odpowiedzi:


6

Biorąc pod uwagę, że nie jestem pewien co do znaczenia / konieczności async, tak naprawdę nie mogę się z tym kłócić, ale moja najlepsza propozycja zastąpienia awaitto:

yield while (patrz! brak nowych słów kluczowych)

Zauważmy, że zastanowiwszy się nad tym trochę, zastanawiam się, czy ponowne użycie whilew ten sposób jest dobrym pomysłem - naturalną tendencją byłoby oczekiwanie później wartości logicznej.

(Myśli: znalezienie dobrych słów kluczowych jest jak znalezienie dobrych nazw domen :)


+1, a tak przy okazji, pobiłeś mnie do komentowania jego wpisu na blogu o 7 minut ...
Uwaga dla siebie - wymyśl imię

Ale niekoniecznie dajesz wykonanie, jeśli zadanie zostało już zakończone. Ale zawsze czekasz na zakończenie zadania (choć nigdy nie czekasz).
Allon Guralnek

@ Allon Nie musisz uruchamiać żadnego z elementów pętli while(x) {...}, jeśli xjest to fałsz.
Uwaga dla siebie - wymyśl imię

@ Uwaga: Cóż, nie ma czasownika while. Jeśli dodasz czasownik, na przykład do, otrzymasz do {...} while (x), który wykonuje ciało niezależnie od x (przynajmniej raz). Twoja sugestia yield whilewydaje się bardzo podobna do do while, ale z przeciwnymi gwarancjami wykonywania czasownika, co może być nieco mylące (ale niezbyt wielka sprawa). Najbardziej nie lubię tego yield, że implikuje wdrożenie mechanizmu. Sensem async/ awaitjest to, że piszesz asynchroniczną operację w synchronicznym stylu. yieldprzełamuje ten styl synchroniczny.
Allon Guralnek

Czy nowe słowo kluczowe jest koniecznie złe? Jak rozumiem, awaitsłowo kluczowe zostanie rozpoznane na podstawie kontekstu, więc nadal możesz mieć metodę lub zmienną o nazwie „czekaj”, jeśli chcesz. W pewnym stopniu uważam, że użycie nowego słowa kluczowego do nowej funkcjonalności jest mniej mylące niż ponowne użycie istniejącego słowa kluczowego w celu oznaczenia więcej niż jednej rzeczy. (przesadzony przykład: dangermouse.net/esoteric/ook.html )
Tim Goodman

5

Co powiesz na brak słowa kluczowego?

Chciałbym, aby kompilator zdał sobie sprawę, że przez większość czasu, gdy wywołuję metodę asynchroniczną, chcę jej wynik.

Document doc = DownloadDocumentAsync();

Otóż ​​to. Powodem, dla którego ludzie mają trudności z wymyśleniem słowa kluczowego dla tej rzeczy, jest to, że jest to słowo kluczowe dla „rób to, co zrobiłbyś, gdyby wszystko było całkowicie normalne”. To powinno być domyślne, nie wymagać słowa kluczowego.

Aktualizacja

Początkowo zasugerowałem, że kompilator powinien być sprytny z wnioskami o typ, aby dowiedzieć się, co robić. Zastanawiając się nad tym, chciałbym zachować obecną implementację w CTP w obecnej postaci, ale dodam do niej kilka trywialnych dodatków, aby zmniejszyć liczbę przypadków, w których konieczne byłoby awaitjawne użycie słowa kluczowego.

Wymyślamy atrybut: [AutoAwait]. Można to zastosować tylko do metod. Jednym ze sposobów, aby zastosować to do metody, jest jej oznaczenie async. Ale możesz to również zrobić ręcznie, np .:

[AutoAwait]
public Task<Document> DownloadDocumentAsync()

Następnie w każdej asyncmetodzie kompilator zakłada, że ​​chcesz poczekać na wywołanie DownloadDocumentAsync, więc nie musisz go określać. Każde wywołanie tej metody będzie automatycznie na nią czekało.

Document doc = DownloadDocumentAsync();

Teraz, jeśli chcesz „uzyskać spryt” i uzyskać Task<Document>, użyj operatora start, który może pojawić się tylko przed wywołaniem metody:

Task<Document> task = start DownloadDocumentAsync();

Zgrabnie, tak myślę. Teraz zwykłe wywołanie metody oznacza to, co zwykle oznacza: poczekaj na zakończenie metody. I startwskazuje coś innego: nie czekaj.

W przypadku kodu, który pojawia się poza asyncmetodą, jedynym sposobem na wywołanie [AutoAwait]metody jest przedrostek start. Zmusza to do pisania kodu o tym samym znaczeniu, niezależnie od tego, czy pojawia się w asyncmetodzie, czy nie.

Potem zaczynam być chciwy! :)

Po pierwsze, chcę asynczastosować się do metod interfejsu:

interface IThing
{
    async int GetCount();
} 

Zasadniczo oznacza to, że metoda implementująca musi zwrócić Task<int>lub coś kompatybilnego await, a osoby wywołujące metodę otrzymają [AutoAwait]zachowanie.

Również po wdrożeniu powyższej metody chcę móc pisać:

async int GetCount()

Więc nie muszę wspominać Task<int>o typie zwrotu.

Chcę asyncrównież zastosować do typów delegowanych (które przecież są jak interfejsy z jedną metodą). Więc:

public async delegate TResult AsyncFunc<TResult>();

asyncDelegat posiada - zgadliście - [AutoAwait]zachowanie. Z asyncmetody możesz ją wywołać, a zostanie ona automatycznie awaitedytowana (chyba że zdecydujesz się to startzrobić). A więc jeśli powiesz:

AsyncFunc<Document> getDoc = DownloadDocumentAsync;

To po prostu działa. To nie jest wywołanie metody. Żadne zadanie nie zostało jeszcze uruchomione - async delegatenie jest zadaniem. To fabryka do wykonywania zadań. Możesz powiedzieć:

Document doc = getDoc();

A to rozpocznie zadanie i zaczekaj, aż się zakończy i da ci wynik. Lub możesz powiedzieć:

Task<Document> t = start getDoc();

Tak więc jednym z miejsc, w których przecieka „hydraulika”, jest to, że jeśli chcesz delegować asyncmetodę, musisz wiedzieć, że używasz async delegatetypu. Zamiast tego Funcmusisz powiedzieć AsyncFunci tak dalej. Chociaż pewnego dnia tego rodzaju rzeczy mogą zostać naprawione przez lepsze wnioskowanie o typie.

Innym pytaniem jest, co powinno się stać, jeśli powiesz, że zacznij od zwykłej metody (nie asynchronicznej). Oczywiście błąd kompilacji byłby bezpieczną opcją. Ale są też inne możliwości.


Może to być wykonalne przy niejawnej konwersji, ale w innym przypadku wymagałoby oceny instrukcji od lewej do prawej (co jest dokładnie przeciwne do tego, jak zwykle działa kompilator, z wyjątkiem lambdas). Myślę, że nadal byłbym temu przeciwny, ponieważ uniemożliwia użycie var, potencjalnie konieczność zastąpienia długiej, jawnej nazwy typu, a także jest niejednoznaczny między awaitprzypadkiem a przypadkiem, w którym ktoś przypadkowo wywołał metodę asynchroniczną zamiast normalnej metody synchronicznej. Z początku wydaje się intuicyjny, ale w rzeczywistości narusza zasadę najmniejszego zaskoczenia.
Aaronaught

@Aaronaught - Dlaczego zapobiega użyciu var? Zastanawiam się, czy odpowiadasz na poprzednią wersję mojej odpowiedzi ... Całkowicie ją przepisałem. Możesz teraz pomyśleć o tej sugestii w następujący sposób: jeśli metoda jest oznaczona specjalnym atrybutem, to tak, jakby awaitsłowo kluczowe było automatycznie wstawiane przed wywołaniami tej metody (chyba że pominiesz to startprzedrostkiem). Wszystko pozostaje dokładnie tak, jak w CTP, a zatem vardziała dobrze.
Daniel Earwicker

Rzeczywiście byłem ... dziwny, że postanowiłem ponownie odwiedzić ten wątek i odpowiedzieć na twoją odpowiedź prawie dokładnie w tym samym czasie, kiedy zdecydowałeś się go edytować. Będę musiał go ponownie przeczytać ...
Aaronaught

1
Podoba mi się twoja inwersja słowa kluczowego oczekującego. Nie lubię też potrójnej redundancji public async Task<int> FooAsync().
Allon Guralnek

1
Tak, widzę, że konwencja nazewnictwa Async-postfix jest znakiem, że coś można uchwycić bardziej formalnie. Zasadniczo, jeśli istnieje reguła mówiąca: „metody takie jak ta powinny być nazwane w określony sposób, aby ludzie wiedzieli, jak je odpowiednio wywoływać”, oznacza to, że tę samą regułę można zastosować do przypisania tych metod w określony sposób, a następnie kompilatora do pomogą ci właściwie do nich zadzwonić.
Daniel Earwicker


4

Myślę, że asyncjest w porządku, ale może dlatego, że kojarzę go ze stronami asynchronicznymi ASP.NET - ten sam pomysł.

Dla awaitsłowa kluczowego wolę continue afterlub resume after.

I nie jak yieldlub któregokolwiek z jej wariantów, ponieważ semantyka są takie, że metoda nie może faktycznie uzyskując wykonanie; zależy to od stanu zadania.


Lubię resume afterza await. Może asyncmógłby być nazwany resumable.
Tim Goodman,

Chociaż wolałbym tylko after, continue afterpodejście to ma dużą zaletę implementacyjną: obejmuje obecnie istniejące słowo kluczowe kontekstowe, ale ze składnią niezgodną z bieżącym użyciem. To gwarantuje, że dodanie nigdy nie zepsuje istniejącego kodu. W przypadku użycia zupełnie nowego słowa kluczowego implementacja musi poradzić sobie z możliwym wykorzystaniem tego słowa jako identyfikatora starszego kodu, co może być dość trudne.
Edurne Pascual

3

Dodałem też do komentarzy na blogu Erica, nie widzę problemów z używaniem tego samego słowa kluczowego async

var data = async DownloadFileAsync(url);

Wyrażam tylko, że chcę asynchronicznie pobrać plik. Jest tu trochę redundancji, „asynchronizacja” pojawia się dwa razy, ponieważ jest również w nazwie metody. Kompilator może być wyjątkowo sprytny i wykryć konwencję, że metody kończące się na „Async” są w rzeczywistości metodami asynchronicznymi, i dodać to dla nas w skompilowanym kodzie. Zamiast tego możesz po prostu zadzwonić

var data = async DownloadFile(url);

w przeciwieństwie do wywoływania synchronicznego

var data = DownloadFile(url);

Heck, powinniśmy być w stanie zdefiniować je w ten sam sposób, ponieważ słowo kluczowe async znajduje się w naszej deklaracji, dlaczego musimy ręcznie dodać „Async” do nazwy każdej metody - kompilator może to zrobić za nas.


Podoba mi się cukier, który dodałeś, chociaż faceci z C # prawdopodobnie nie wybiorą go. (FWIW, robią już coś podobnego, szukając nazw atrybutów)
Uwaga dla siebie -

2
Biorąc pod uwagę, że asyncsłowo kluczowe w metodzie jest po prostu subtelne (jeśli dobrze zrozumiałem), zastanawiam się, czy najlepszą rzeczą nie byłoby zrobić coś przeciwnego do tego, co sugerujesz: porzucić asyncmetodę i po prostu użyć jej gdzie obecnie mają await.
Benjol

To jest możliwe. Słowo kluczowe async w metodzie delcaration ma na celu zapewnienie czystości. Wolałbym, żeby tak było, ale bez wymogu dodawania „Async” do nazw metod. Np. async Task<Byte[]> DownloadFile(...)Zamiast Task<Byte[]> DownloadFileAsync(...)(ten ostatni i tak będzie skompilowanym podpisem). Tak czy inaczej działa.
Mark H

Szczerze mówiąc, nie jestem też fanem tego. Tak jak w poprzednim komentarzu, muszę zaznaczyć, że twoja ostateczna wersja narusza zasadę najmniejszego zaskoczenia, ponieważ w rzeczywistości odwołuje się ona do zupełnie innej metody niż ta napisana, a implementacja i podpis tej metody zależy całkowicie od klasy implementującej . Nawet pierwsza wersja jest problematyczna, ponieważ tak naprawdę nie mówi nic (asynchronicznie wykonać tę metodę asynchroniczną?). Myśl, którą staramy się wyrazić, jest kontynuacją lub odroczeniem wykonania i wcale tego nie wyraża.
Aaronaught

3

async = zadanie - modyfikuje funkcję, aby zwrócić zadanie, więc dlaczego nie użyć słowa kluczowego „zadanie”?

czekaj = zakończ - niekoniecznie musimy czekać, ale zadanie musi „zakończyć” przed użyciem wyniku.


Naprawdę trudno jest tutaj kłócić się z prostotą.
sblom

2

Lubię yield until. yield while, już zasugerowany, jest świetny i nie wprowadza żadnych nowych słów kluczowych, ale myślę, że „dopóki” nie uchwyci zachowania nieco lepiej.

Uważam, że yield <something>to świetny pomysł, ponieważ wydajność już uchwyciła ideę uczynienia reszty metody tak dobrą kontynuacją. Może ktoś wymyśli lepsze słowo niż „do”.


2

Chcę tylko zarejestrować swój głos na sugestię Aarona G. comefrom- pierwsze właściwe zastosowanie, jakie widziałem w oświadczeniu INTERCAL COMEFROM . Chodzi o to, że jest to przeciwieństwo GOTO ( odskakiwanie od instrukcji GOTO), ponieważ powoduje, że pewne miejsce w kodzie przeskakuje do instrukcji COMEFROM.


2

Skoro mamy do czynienia z Task<T>s, to jak użyć startsłowa kluczowego poprzedzającego instrukcję, jak w:

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


Hmmm, może finishbyłby nawet lepszy niż start?
Protagonista

1

Warto zauważyć, że F # używa również asyncsłowa kluczowego w swoich obiegach pracy asynchronicznej, co jest właściwie dokładnie tym samym, co nowa funkcja asynchroniczna w C # 5. Dlatego nie zmieniłbym tego

W przypadku awaitsłowa kluczowego w języku F # używają po prostu let!zamiast let. C # nie ma tej samej składni przypisania, więc potrzebują czegoś po prawej stronie =znaku. Jak powiedział Benjol, działa tak samo, yieldwięc powinien być niemal wariantem tego.


1
„Oczekiwanie” wcale nie musi być przypisane (choć oczywiście tak jest.) Jest to zgodne z prawem jako operator niemal każdego wyrażenia, w którym można znaleźć GetAwaiter. (Dokładne zasady nie zostały jeszcze opracowane w formie możliwej do opublikowania).
Eric Lippert

1
@Eric, w F # byłoby do!, ale wiedziałeś, że ...
Benjol

1

yield async FetchAsync(..)


To doskonale pasuje do asyncmodyfikatora, który musisz zastosować do metody, którą wywołujesz. A także semantyczny prądu yield return, to znaczy, że zwracasz i dajesz wykonanie kodowi wyliczającemu, podczas gdy w tym przypadku dajesz swoje wykonanie metodzie asynchronicznej.

Wyobraź sobie, że w przyszłości będą inne zastosowania yield, moglibyśmy dodać yield xgdzie x jest lśniącą nową funkcją zamiast posiadania tych wszystkich różnych słów kluczowych do robienia głównie tego samego, czyli wykonywania wydajności.

Szczerze mówiąc, nie do końca rozumiem argument „nie poddawanie się”. W końcu, czy nie ma sensu wywoływać innej metody w celu „wykonania” tej metody? Niezależnie od tego, czy jest asynchroniczny czy nie? Coś tu brakuje?

I dobrze, jeśli asynczwroty są synchroniczne, ale słowo kluczowe powinno oznaczać, że istnieje prawdopodobieństwo, że metoda będzie działać asynchronicznie i że wykonasz wykonanie innej metody. Twoja metoda powinna to uwzględnić niezależnie od tego, czy metoda faktycznie wykonuje wywołania asynchroniczne, czy nie.

IMO Myślę, że różne przypadki „niepoddawania się” są szczegółami wdrożenia. Wolę ręczyć za spójność w języku (tj. Ponowne użycie yield).


0

A może completejak w „Chcę, aby zadanie zostało wykonane”?

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

1
Dlaczego głosowanie negatywne? Uprzejmość polega na przynajmniej wytłumaczeniu się po przegłosowaniu.
Allon Guralnek

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.