ostrzeżenie to wywołanie nie jest oczekiwane, wykonywanie bieżącej metody jest kontynuowane

142

Właśnie dostałem VS2012 i próbuję sobie poradzić async.

Powiedzmy, że mam metodę, która pobiera pewną wartość z blokującego źródła. Nie chcę, aby obiekt wywołujący metodę był blokowany. Mógłbym napisać metodę, która odbiera wywołanie zwrotne, które jest wywoływane, gdy nadejdzie wartość, ale ponieważ używam C # 5, decyduję się na asynchronizację metody, aby wywołujący nie musieli zajmować się wywołaniami zwrotnymi:

// contrived example (edited in response to Servy's comment)
public static Task<string> PromptForStringAsync(string prompt)
{
    return Task.Factory.StartNew(() => {
        Console.Write(prompt);
        return Console.ReadLine();
    });
}

Oto przykładowa metoda, która to wywołuje. Gdyby PromptForStringAsyncnie była asynchroniczna, ta metoda wymagałaby zagnieżdżenia wywołania zwrotnego w wywołaniu zwrotnym. Dzięki async mogę napisać swoją metodę w bardzo naturalny sposób:

public static async Task GetNameAsync()
{
    string firstname = await PromptForStringAsync("Enter your first name: ");
    Console.WriteLine("Welcome {0}.", firstname);

    string lastname = await PromptForStringAsync("Enter your last name: ");
    Console.WriteLine("Name saved as '{0} {1}'.", firstname, lastname);
}

Na razie w porządku. Problemem jest to, kiedy zadzwonić GetNameAsync:

public static void DoStuff()
{
    GetNameAsync();
    MainWorkOfApplicationIDontWantBlocked();
}

Chodzi o GetNameAsyncto, że jest asynchroniczny. Nie chcę , aby było blokowane, ponieważ chcę wrócić do MainWorkOfApplicationIDontWantBlocked ASAP i pozwolić GetNameAsync robić swoje w tle. Jednak nazwanie tego w ten sposób daje mi ostrzeżenie kompilatora w GetNameAsyncwierszu:

Warning 1   Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Doskonale zdaję sobie sprawę, że „wykonywanie aktualnej metody jest kontynuowane przed zakończeniem wywołania”. To jest punkt kodu asynchronicznego, prawda?

Wolę, aby mój kod kompilował się bez ostrzeżeń, ale nie ma tu nic do „naprawienia”, ponieważ kod robi dokładnie to, co zamierzam. Mogę pozbyć się ostrzeżenia, przechowując zwracaną wartość GetNameAsync:

public static void DoStuff()
{
    var result = GetNameAsync(); // supress warning
    MainWorkOfApplicationIDontWantBlocked();
}

Ale teraz mam zbędny kod. Visual Studio wydaje się rozumieć, że byłem zmuszony napisać ten niepotrzebny kod, ponieważ pomija on normalne ostrzeżenie „wartość nigdy nie używana”.

Mogę również pozbyć się ostrzeżenia, opakowując GetNameAsync w metodę, która nie jest asynchroniczna:

    public static Task GetNameWrapper()
    {
        return GetNameAsync();
    }

Ale to jeszcze bardziej zbędny kod. Muszę więc pisać kod, którego nie potrzebuję ani nie toleruję niepotrzebnego ostrzeżenia.

Czy jest coś złego w używaniu async?

Błoto
źródło
2
BTW, podczas wdrażania PromptForStringAsyncwykonujesz więcej pracy, niż potrzebujesz; po prostu zwróć wynik Task.Factory.StartNew. Jest to już zadanie, którego wartością jest ciąg wprowadzony w konsoli. Nie ma potrzeby czekać, aż zwróci wynik; takie postępowanie nie dodaje nowej wartości.
Servy
Wwouldn't to więcej sensu za GetNameAsynczapewnienie pełnej nazwy, które zostały dostarczone przez użytkownika (czyli Task<Name>, zamiast po prostu zwrócenie Task? DoStuffMogłyby następnie zapisać to zadanie, i albo awaitgo po drugiej metodzie, a nawet przejść do innego zadania metoda, więc mogłaby awaitlub Waitgdzieś w środku swojej implementacji
Servy
@Servy: Jeśli po prostu zwrócę zadanie, pojawi się błąd „Ponieważ jest to metoda asynchroniczna, wyrażenie zwracane musi być typu„ string ”, a nie„ Task <string> ””.
Błoto
1
Usuń asyncsłowo kluczowe.
Servy
15
IMO, to był kiepski wybór, jeśli chodzi o ostrzeżenie ze strony zespołu C #. Ostrzeżenia powinny dotyczyć rzeczy, które prawie na pewno są złe. Jest wiele przypadków, w których chcesz „odpalić i zapomnieć” o metodzie asynchronicznej, a czasem tak naprawdę chcesz na nią poczekać.
MgSam

Odpowiedzi:

106

Jeśli naprawdę nie potrzebujesz wyniku, możesz po prostu zmienić GetNameAsyncpodpis użytkownika na zwracany void:

public static async void GetNameAsync()
{
    ...
}

Rozważ odpowiedź na powiązane pytanie: Jaka jest różnica między zwróceniem pustki a zwróceniem zadania?

Aktualizacja

Jeśli potrzebujesz wyniku, możesz zmienić GetNameAsynczwracany, powiedzmy Task<string>:

public static async Task<string> GetNameAsync()
{
    string firstname = await PromptForStringAsync("Enter your first name: ");
    string lastname = await PromptForStringAsync("Enter your last name: ");
    return firstname + lastname;
}

I użyj go w następujący sposób:

public static void DoStuff()
{
    Task<string> task = GetNameAsync();

    // Set up a continuation BEFORE MainWorkOfApplicationIDontWantBlocked
    Task anotherTask = task.ContinueWith(r => {
            Console.WriteLine(r.Result);
        });

    MainWorkOfApplicationIDontWantBlocked();

    // OR wait for the result AFTER
    string result = task.Result;
}
Nikolay Khil
źródło
16
Byłoby tak tylko wtedy, gdyby nigdy nie dbał o wynik, w przeciwieństwie do tego, że nie potrzebowałby wyniku tylko raz .
Servy
3
@Servy, dobrze, dzięki za wyjaśnienie. Ale OP GetNameAsyncnie zwraca żadnej wartości (z wyjątkiem samego wyniku, oczywiście).
Nikolay Khil
2
Dobrze, ale zwracając zadanie, może wiedzieć, kiedy zakończy ono wykonywanie wszystkich operacji asynchronicznych. Jeśli wróci void, nie będzie wiedział, kiedy się skończy. To właśnie miałem na myśli, mówiąc „wynik” w moim poprzednim komentarzu.
Servy
31
Zgodnie z ogólną zasadą nigdy nie powinieneś mieć async voidmetody z wyjątkiem programów obsługi zdarzeń.
Daniel Mann
2
Inną rzeczą, na którą należy zwrócić uwagę, jest to, że zachowanie jest inne, jeśli chodzi o niezauważone wyjątki, gdy przełączenie na async voiddowolny wyjątek, którego nie złapiesz, spowoduje awarię procesu, ale w .net 4.5 będzie on nadal działał.
Caleb Vear,
68

Spóźniłem się na tę dyskusję, ale jest też opcja użycia #pragmadyrektywy preprocesora. Mam tu i ówdzie kod asynchroniczny, na który wyraźnie nie chcę czekać w pewnych warunkach i nie lubię ostrzeżeń i nieużywanych zmiennych, tak jak reszta z was:

#pragma warning disable 4014
SomeMethodAsync();
#pragma warning restore 4014

"4014"Pochodzi z tej strony MSDN: Kompilator Ostrzeżenie (poziom 1) CS4014 .

Zobacz także ostrzeżenie / odpowiedź @ ryan-horath tutaj https://stackoverflow.com/a/12145047/928483 .

Wyjątki zgłoszone podczas wywołania asynchronicznego, które nie są oczekiwane, zostaną utracone. Aby pozbyć się tego ostrzeżenia, należy przypisać wartość zwracaną przez zadanie wywołania asynchronicznego do zmiennej. Zapewnia to dostęp do wszystkich zgłoszonych wyjątków, które zostaną wskazane w zwracanej wartości.

Aktualizacja dla C # 7.0

C # 7.0 dodaje nową funkcję, odrzucaj zmienne: Odrzucenia - przewodnik C # , który również może pomóc w tym zakresie.

_ = SomeMethodAsync();
Willem
źródło
6
To jest absolutnie niesamowite. Nie miałem pojęcia, że ​​możesz to zrobić. Dziękuję Ci!
Maxim Gershkovich
3
Nie trzeba nawet mówić var, po prostu napisz_ = SomeMethodAsync();
Ray
Podkreśl - jak w go - fajnie
Stefan Steiger
43

Nie przepadam za rozwiązaniami, które albo przypisują zadanie do nieużywanej zmiennej, albo zmieniają sygnaturę metody, aby zwracała void. Pierwsza tworzy zbędny, nieintuicyjny kod, podczas gdy druga może nie być możliwa, jeśli implementujesz interfejs lub masz inne użycie funkcji, w której chcesz użyć zwróconego zadania.

Moim rozwiązaniem jest utworzenie metody rozszerzającej Task o nazwie DoNotAwait (), która nic nie robi. To nie tylko pominie wszystkie ostrzeżenia, ReSharper lub inne, ale sprawi, że kod będzie bardziej zrozumiały i wskaże przyszłym opiekunom twojego kodu, że naprawdę chciałeś, aby wywołanie nie było oczekiwane.

Metoda przedłużenia:

public static class TaskExtensions
{
    public static void DoNotAwait(this Task task) { }
}

Stosowanie:

public static void DoStuff()
{
    GetNameAsync().DoNotAwait();
    MainWorkOfApplicationIDontWantBlocked();
}

Edytowano, aby dodać: jest to podobne do rozwiązania Jonathana Allena, w którym metoda rozszerzająca uruchomiłaby zadanie, jeśli nie została jeszcze rozpoczęta, ale wolę mieć funkcje o jednym celu, aby intencja osoby dzwoniącej była całkowicie jasna.

Joe Savage
źródło
1
Podoba mi się to, ale zmieniłem nazwę na Unawait ();)
Ostati
29

async void JEST ZŁY!

  1. Jaka jest różnica między zwrotem anulowania a zwróceniem zadania?
  2. https://jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.html

Sugeruję, abyś jawnie uruchomił Taskanonimową metodę ...

na przykład

public static void DoStuff()
{
    Task.Run(async () => GetNameAsync());
    MainWorkOfApplicationIDontWantBlocked();
}

Lub jeśli chcesz, aby się zablokował, możesz poczekać na metodę anonimową

public static void DoStuff()
{
    Task.Run(async () => await GetNameAsync());
    MainWorkOfApplicationThatWillBeBlocked();
}

Jeśli jednak twoja GetNameAsyncmetoda musi wchodzić w interakcję z interfejsem użytkownika lub nawet cokolwiek związanego z interfejsem użytkownika (WINRT / MVVM, patrzę na ciebie), to robi się trochę fajniej =)

Będziesz musiał przekazać odwołanie do dyspozytora interfejsu użytkownika w ten sposób ...

Task.Run(async () => await GetNameAsync(CoreApplication.MainView.CoreWindow.Dispatcher));

A następnie w metodzie asynchronicznej będziesz musiał wchodzić w interakcję z interfejsem użytkownika lub elementami związanymi z interfejsem użytkownika, który pomyślał, że dyspozytor ...

dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {  this.UserName = userName; });
Panowie
źródło
Twoje rozszerzenie zadań jest niesamowite. Nie wiem, dlaczego wcześniej go nie wdrożyłem.
Mikael Dúi Bolinder
10
Pierwsze podejście, o którym wspomniałeś, powoduje inne ostrzeżenie: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread. powoduje to również utworzenie nowego wątku, podczas gdy nowy wątek niekoniecznie zostanie utworzony z samym async / await.
gregsdennis
Powinien pan wstać!
Ozkan
17

Oto, co obecnie robię:

SomeAyncFunction().RunConcurrently();

Gdzie RunConcurrentlyjest zdefiniowany jako ...

 /// <summary> 
 /// Runs the Task in a concurrent thread without waiting for it to complete. This will start the task if it is not already running. 
 /// </summary> 
 /// <param name="task">The task to run.</param> 
 /// <remarks>This is usually used to avoid warning messages about not waiting for the task to complete.</remarks> 
 public static void RunConcurrently(this Task task) 
 { 
     if (task == null) 
         throw new ArgumentNullException("task", "task is null."); 

     if (task.Status == TaskStatus.Created) 
         task.Start(); 
 } 

https://github.com/docevaad/Anchor/blob/master/Tortuga.Anchor/Tortuga.Anchor.source/shared/TaskUtilities.cs

https://www.nuget.org/packages/Tortuga.Anchor/

Jonathan Allen
źródło
1
+1. Wygląda na to, że unika się wszystkich problemów porównywanych w komentarzach do innych odpowiedzi. Dzięki.
Grault
3
Potrzebujesz tylko tego: public static void Forget(this Task task) { }
Shital Shah
1
co masz na myśli @ShitalShah
Jay Wick
@ShitalShah, który będzie działał tylko z zadaniami automatycznego uruchamiania, takimi jak te utworzone w async Task. Niektóre zadania trzeba uruchamiać ręcznie.
Jonathan Allen,
7

Zgodnie z artykułem firmy Microsoft dotyczącym tego ostrzeżenia, możesz go rozwiązać, po prostu przypisując zwrócone zadanie do zmiennej. Poniżej znajduje się tłumaczenie kodu podanego w przykładzie Microsoft:

    // To suppress the warning without awaiting, you can assign the 
    // returned task to a variable. The assignment doesn't change how
    // the program runs. However, the recommended practice is always to
    // await a call to an async method.
    // Replace Call #1 with the following line.
    Task delayTask = CalledMethodAsync(delay);

Zwróć uwagę, że spowoduje to wyświetlenie komunikatu „Zmienna lokalna nigdy nie jest używana” w programie ReSharper.

devlord
źródło
Tak, to tłumi ostrzeżenie, ale nie, to tak naprawdę niczego nie rozwiązuje. Task-powracające funkcje powinny być awaitwyłączone, chyba że masz bardzo dobry powód, aby tego nie robić . Nie ma tutaj powodu, dla którego odrzucenie zadania byłoby lepsze niż zaakceptowana już odpowiedź użycia async voidmetody.
Wolę też metodę void. To sprawia, że ​​StackOverflow jest mądrzejszy niż Microsoft, ale oczywiście powinno być.
devlord
5
async voidwprowadza poważne problemy związane z obsługą błędów i skutkuje niezdatnym do testowania kodem (zobacz mój artykuł MSDN ). Byłoby znacznie lepiej użyć zmiennej - jeśli masz absolutną pewność , że chcesz, aby wyjątki zostały po cichu połknięte. Bardziej prawdopodobne jest, że operator chciałby rozpocząć dwie Tasksekundy, a następnie wykonać await Task.WhenAll.
Stephen Cleary
@StephenCleary To, czy wyjątki od niezauważonych zadań mają być ignorowane, można skonfigurować. Jeśli jest jakakolwiek szansa, że ​​Twój kod zostanie użyty w czyimś projekcie, nie pozwól, aby tak się stało. async void DoNotWait(Task t) { await t; }Aby uniknąć wad async voidmetod, które opisujesz, można użyć prostej metody pomocniczej . (I nie sądzę, że Task.WhenAlltego chce OP, ale bardzo dobrze może być.)
@hvd: Twoja metoda pomocnicza sprawiłaby, że byłaby testowalna, ale nadal miałaby bardzo słabą obsługę wyjątków.
Stephen Cleary
3

Tutaj proste rozwiązanie.

public static class TasksExtensions
{
    public static void RunAndForget(this Task task)
    {
    }
}

pozdrowienia

JuanluElGuerre
źródło
1

To twój uproszczony przykład powoduje powstanie zbędnego kodu. Zwykle chciałbyś użyć danych, które zostały pobrane ze źródła blokującego w pewnym momencie w programie, więc chciałbyś, aby wynik był z powrotem, aby można było dostać się do danych.

Jeśli naprawdę masz coś, co dzieje się całkowicie odizolowane od reszty programu, asynchronizacja nie byłaby właściwym podejściem. Po prostu rozpocznij nowy wątek dla tego zadania.

Guffa
źródło
I zrobić chcą wykorzystać dane pobrane ze źródła blokującego, ale nie chcę, aby zablokować rozmówcę, a ja na to czekać. Bez asynchroniczności można to osiągnąć, przekazując wywołanie zwrotne. W moim przykładzie mam dwie metody asynchroniczne, które muszą być wywoływane po kolei, a drugie wywołanie musi użyć wartości zwróconej przez pierwsze. Oznaczałoby to zagnieżdżanie programów obsługi wywołań zwrotnych, co jest brzydkie. Dokumentacja, którą czytałem, sugeruje, że właśnie to asynczostało zaprojektowane do czyszczenia ( na przykład )
Błoto,
@Mud: Po prostu wyślij wynik z pierwszego wywołania asynchronicznego jako parametr do drugiego wywołania. W ten sposób kod w drugiej metodzie zaczyna się od razu i może czekać na wynik pierwszego wywołania, kiedy ma na to ochotę.
Guffa,
Mogę to zrobić, ale bez asynchroniczności oznacza to zagnieżdżone wywołania zwrotne, na przykład: MethodWithCallback((result1) => { Use(result1); MethodWithCallback((result2) => { Use(result1,result2); })Nawet w tym trywialnym przykładzie analizowanie jest dziwne. W przypadku async generowany jest dla mnie równoważny kod, result1 = await AsyncMethod(); Use(result1); result2 = await AsyncMethod(); Use(result1,result2); który jest o wiele łatwiejszy do odczytania (choć żadne z nich nie jest bardzo czytelne w tym komentarzu!)
Błoto,
@ Błoto: Tak. Ale możesz wywołać drugą metodę asynchroniczną zaraz po pierwszej, nie ma powodu, aby czekała na wywołanie synchroniczne Use.
Guffa,
Naprawdę nie rozumiem tego, co mówisz. MethodWithCallback jest asynchroniczna. Jeśli oddzwonię do nich z powrotem, nie czekając na pierwsze połączenie, drugie połączenie może zakończyć się przed pierwszym. Jednak potrzebuję wyniku pierwszego wywołania w programie obsługi dla drugiego. Więc muszę czekać na pierwszy telefon.
Błoto
0

Jeśli nie chcesz zmieniać sygnatury metody na zwracaną void(ponieważ zwracanie voidpowinno być zawsze unieważnione ), możesz użyć takiej funkcji C # 7.0+ Discard , która jest nieco lepsza niż przypisywanie do zmiennej (i powinna usunąć większość innych ostrzeżenia narzędzi do weryfikacji źródła):

public static void DoStuff()
{
    _ = GetNameAsync(); // we don't need the return value (suppresses warning)
    MainWorkOfApplicationIDontWantBlocked();
}
Simon Mourier
źródło