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 PromptForStringAsync
nie 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 GetNameAsync
to, ż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 GetNameAsync
wierszu:
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?
źródło
PromptForStringAsync
wykonujesz więcej pracy, niż potrzebujesz; po prostu zwróć wynikTask.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.GetNameAsync
zapewnienie pełnej nazwy, które zostały dostarczone przez użytkownika (czyliTask<Name>
, zamiast po prostu zwrócenieTask
?DoStuff
Mogłyby następnie zapisać to zadanie, i alboawait
go po drugiej metodzie, a nawet przejść do innego zadania metoda, więc mogłabyawait
lubWait
gdzieś w środku swojej implementacjiasync
słowo kluczowe.Odpowiedzi:
Jeśli naprawdę nie potrzebujesz wyniku, możesz po prostu zmienić
GetNameAsync
podpis użytkownika na zwracanyvoid
: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ć
GetNameAsync
zwracany, powiedzmyTask<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; }
źródło
GetNameAsync
nie zwraca żadnej wartości (z wyjątkiem samego wyniku, oczywiście).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.async void
metody z wyjątkiem programów obsługi zdarzeń.async void
dowolny wyjątek, którego nie złapiesz, spowoduje awarię procesu, ale w .net 4.5 będzie on nadal działał.Spóźniłem się na tę dyskusję, ale jest też opcja użycia
#pragma
dyrektywy 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 .
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.
źródło
var
, po prostu napisz_ = SomeMethodAsync();
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.
źródło
async void
JEST ZŁY!Sugeruję, abyś jawnie uruchomił
Task
anonimową 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
GetNameAsync
metoda 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; });
źródło
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.Oto, co obecnie robię:
Gdzie
RunConcurrently
jest 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/
źródło
public static void Forget(this Task task) { }
async Task
. Niektóre zadania trzeba uruchamiać ręcznie.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.
źródło
Task
-powracające funkcje powinny byćawait
wyłą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życiaasync void
metody.async void
wprowadza 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ąć dwieTask
sekundy, a następnie wykonaćawait Task.WhenAll
.async void DoNotWait(Task t) { await t; }
Aby uniknąć wadasync void
metod, które opisujesz, można użyć prostej metody pomocniczej . (I nie sądzę, żeTask.WhenAll
tego chce OP, ale bardzo dobrze może być.)Tutaj proste rozwiązanie.
public static class TasksExtensions { public static void RunAndForget(this Task task) { } }
pozdrowienia
źródło
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.
źródło
async
zostało zaprojektowane do czyszczenia ( 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!)Use
.Czy na pewno chcesz zignorować wynik? na przykład ignorowanie nieoczekiwanych wyjątków?
Jeśli nie, możesz rzucić okiem na to pytanie: podejście „Ogień i zapomnij” ,
źródło
Jeśli nie chcesz zmieniać sygnatury metody na zwracaną
void
(ponieważ zwracanievoid
powinno 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(); }
źródło