async / czekaj - kiedy zwrócić zadanie kontra nieważne?


502

W jakich scenariuszach chcielibyśmy skorzystać

public async Task AsyncMethod(int num)

zamiast

public async void AsyncMethod(int num)

Jedyny scenariusz, o którym mogę pomyśleć, to jeśli potrzebujesz zadania, aby móc śledzić jego postępy.

Ponadto w poniższej metodzie asynchronizacja i oczekiwanie na słowa kluczowe są niepotrzebne?

public static async void AsyncMethod2(int num)
{
    await Task.Factory.StartNew(() => Thread.Sleep(num));
}

20
Należy zauważyć, że metody asynchroniczne powinny zawsze być z rozszerzeniem nazwą Async.Example Foo()byłoby stać FooAsync().
Fred

30
@Fred Głównie, ale nie zawsze. To tylko konwencja, a akceptowane wyjątki od tej konwencji dotyczą klas opartych na zdarzeniach lub umów interfejsu, patrz MSDN . Na przykład nie należy zmieniać nazw popularnych programów obsługi zdarzeń, takich jak Button1_Click.
Ben

14
Tylko notatkę, której nie powinieneś używać Thread.Sleepw swoich zadaniach, powinieneś await Task.Delay(num)zamiast tego
Bob Vale

45
@fred Nie zgadzam się z tym, dodawanie sufiksu asynchronicznego przez IMO powinno być stosowane tylko wtedy, gdy udostępniasz interfejs z opcjami synchronizacji i asynchronizacji. Smurf nazywanie rzeczy asynchronicznie, gdy jest tylko jedna intencja, jest bezcelowe. Sprawa w punkcie Task.Delaynie jest Task.AsyncDelayjak wszystkie metody na zadania są asynchroniczny
Nie podobało

11
Miałem ciekawą kwestię tego ranka z metody kontrolera WebAPI 2, został uznany jako async voidzamiast async Task. Metoda uległa awarii, ponieważ korzystała z obiektu kontekstowego Entity Framework zadeklarowanego jako element kontrolera, zanim została zakończona metoda. Środowisko rozproszyło kontroler, zanim zakończyła się jego metoda wykonywania. Zmieniłem metodę na asynchronizującą Zadanie i zadziałało.
costa

Odpowiedzi:


417

1) Zwykle chciałbyś zwrócić a Task. Głównym wyjątkiem powinno być, kiedy trzeba mieć voidtyp zwracany (dla zdarzeń). Jeśli nie ma powodu, by odmawiać dzwoniącemu awaitzadania, dlaczego miałbyś to robić?

2) asyncmetody, które powracają, voidsą wyjątkowe w innym aspekcie: reprezentują operacje asynchroniczne najwyższego poziomu i mają dodatkowe reguły, które wchodzą w grę, gdy zadanie zwróci wyjątek. Najłatwiej jest pokazać różnicę na przykładzie:

static async void f()
{
    await h();
}

static async Task g()
{
    await h();
}

static async Task h()
{
    throw new NotImplementedException();
}

private void button1_Click(object sender, EventArgs e)
{
    f();
}

private void button2_Click(object sender, EventArgs e)
{
    g();
}

private void button3_Click(object sender, EventArgs e)
{
    GC.Collect();
}

fwyjątek jest zawsze „przestrzegany”. Wyjątek, który pozostawia metodę asynchroniczną najwyższego poziomu, jest po prostu traktowany jak każdy inny nieobsługiwany wyjątek. gwyjątek nigdy nie jest przestrzegany. Gdy śmieciarz przychodzi, aby wyczyścić zadanie, widzi, że zadanie spowodowało wyjątek i nikt nie obsługiwał wyjątku. Kiedy tak się dzieje, program TaskScheduler.UnobservedTaskExceptionobsługi działa. Nigdy nie powinieneś na to pozwolić. Aby użyć swojego przykładu,

public static async void AsyncMethod2(int num)
{
    await Task.Factory.StartNew(() => Thread.Sleep(num));
}

Tak, użyj asynci awaittutaj, upewnij się, że twoja metoda nadal działa poprawnie, jeśli zostanie zgłoszony wyjątek.

Aby uzyskać więcej informacji, zobacz: http://msdn.microsoft.com/en-us/magazine/jj991977.aspx


10
Miałem na myśli fzamiast gw moim komentarzu. Wyjątek od fjest przekazywany do SynchronizationContext. gwzrośnie UnobservedTaskException, ale UTEnie powoduje już awarii procesu, jeśli nie jest obsługiwany. Istnieją sytuacje, w których dopuszczalne są takie „wyjątki asynchroniczne”, które są ignorowane.
Stephen Cleary

3
Jeśli masz WhenAnywiele Tasks, co powoduje wyjątki. Często musisz tylko poradzić sobie z pierwszym i często chcesz zignorować pozostałe.
Stephen Cleary

1
@StephenCleary Dzięki, myślę, że to dobry przykład, choć zależy to od powodu, dla którego dzwonisz WhenAnyw pierwszej kolejności, czy w porządku jest zignorowanie innych wyjątków: główny przypadek użycia, który mam dla niego, wciąż kończy się na pozostałych zadaniach po zakończeniu , z wyjątkiem lub bez wyjątku.

10
Jestem trochę zdezorientowany, dlaczego polecasz zwrócenie Zadania zamiast unieważnienia. Jak powiedziałeś, f () wyrzuci i wyjątek, ale g () nie. Czy nie najlepiej jest uświadomić sobie te wyjątki w tle?
user981225,

2
@ user981225 Rzeczywiście, ale to wtedy staje się obowiązkiem osoby dzwoniącej g: każda metoda, która wywołuje g powinna być asynchroniczna i używać również czeka. Jest to wytyczna, a nie twarda reguła, możesz zdecydować, że w twoim programie łatwiej jest unieważnić.

40

Mam natknąć się tym bardzo przydatny artykuł na temat asynci voidnapisany przez Jérôme Labana: https://jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void .html

Najważniejsze jest to, że system async+voidmoże spowodować awarię systemu i zwykle powinien być używany tylko w procedurach obsługi zdarzeń po stronie interfejsu użytkownika.

Powodem tego jest kontekst synchronizacji używany przez AsyncVoidMethodBuilder, którego w tym przykładzie nie ma. Gdy nie ma kontekstowego kontekstu synchronizacji, każdy wyjątek, który nie jest obsługiwany przez treść asynchronicznej metody void, jest ponownie zgłaszany w narzędziu ThreadPool. Chociaż pozornie nie ma innego logicznego miejsca, w którym można by rzucić taki nieobsługiwany wyjątek, niefortunnym efektem jest to, że proces jest kończony, ponieważ nieobsługiwane wyjątki w ThreadPool skutecznie przerywają proces od .NET 2.0. Możesz przechwycić wszystkie nieobsługiwane wyjątki za pomocą zdarzenia AppDomain.UnhandledException, ale nie ma sposobu na odzyskanie procesu po tym zdarzeniu.

Podczas pisania procedur obsługi zdarzeń interfejsu użytkownika metody asynchroniczne void są w pewnym stopniu bezbolesne, ponieważ wyjątki są traktowane w taki sam sposób, jak w metodach niesynchronicznych; są rzucane na Dyspozytora. Istnieje możliwość wyjścia z takich wyjątków, ponieważ w większości przypadków jest to więcej niż poprawne. Jednak poza programami obsługi zdarzeń interfejsu użytkownika metody asynchroniczne void są w jakiś sposób niebezpieczne w użyciu i mogą nie być tak łatwe do znalezienia.


Czy to jest poprawne? Myślałem, że wyjątki w metodach asynchronicznych są wychwytywane w zadaniu. A także afaik, zawsze jest niesłyszący SynchronizationContext
NM

30

Z tego stwierdzenia jasno zrozumiałem.

  1. Metody asynchroniczne void mają różne semantyki obsługi błędów. Gdy wyjątek zostanie wyrzucony z asynchronicznego zadania lub asynchronicznej metody zadania, wyjątek ten jest przechwytywany i umieszczany na obiekcie Task. W przypadku metod async void nie ma obiektu Task, więc wszelkie wyjątki wyrzucone z metody async void będą zgłaszane bezpośrednio w SynchronizationContext (SynchronizationContext reprezentuje miejsce, w którym można wykonać kod.), Który był aktywny, gdy metoda async void zaczęło się

Wyjątków od metody Async Void nie można złapać za pomocą Catch

private async void ThrowExceptionAsync()
{
  throw new InvalidOperationException();
}
public void AsyncVoidExceptions_CannotBeCaughtByCatch()
{
  try
  {
    ThrowExceptionAsync();
  }
  catch (Exception)
  {
    // The exception is never caught here!
    throw;
  }
}

Te wyjątki można zaobserwować za pomocą AppDomain.UnhandledException lub podobnego zdarzenia catch-all dla aplikacji GUI / ASP.NET, ale użycie tych zdarzeń do regularnej obsługi wyjątków jest receptą na niemożność konserwacji (powoduje awarię aplikacji).

  1. Metody asynchronicznej pustki mają różną semantykę komponowania. Metody asynchroniczne zwracające Zadanie lub Zadanie można łatwo skomponować za pomocą funkcji Oczekuj, Task.WhenAny, Task.WhenAll itd. Zwracane nieważne metody asynchroniczne nie zapewniają łatwego sposobu powiadamiania o zakończeniu kodu wywołującego. Łatwo jest uruchomić kilka asynchronicznych metod void, ale nie jest łatwo ustalić, kiedy skończą. Metody asynchroniczne void powiadomią o swoim SynchronizationContext, gdy zaczną i zakończą, ale niestandardowy SynchronizationContext to złożone rozwiązanie dla zwykłego kodu aplikacji.

  2. Metoda Async Void przydatna podczas korzystania z synchronicznej procedury obsługi zdarzeń, ponieważ zgłaszają one wyjątki bezpośrednio w SynchronizationContext, który jest podobny do zachowania synchronicznych procedur obsługi zdarzeń

Aby uzyskać więcej informacji, sprawdź ten link https://msdn.microsoft.com/en-us/magazine/jj991977.aspx


21

Problem z wywoływaniem asynchronicznej nieważności polega na tym, że nawet nie odzyskujesz zadania, nie możesz wiedzieć, kiedy zadanie zostało zakończone (patrz https://blogs.msdn.microsoft.com/oldnewthing/20170720-00/ p = 96655 )

Oto trzy sposoby wywołania funkcji asynchronicznej:

async Task<T> SomethingAsync() { ... return t; }
async Task SomethingAsync() { ... }
async void SomethingAsync() { ... }

We wszystkich przypadkach funkcja jest przekształcana w łańcuch zadań. Różnica polega na tym, co funkcja zwraca.

W pierwszym przypadku funkcja zwraca zadanie, które ostatecznie generuje t.

W drugim przypadku funkcja zwraca zadanie, które nie ma produktu, ale nadal możesz na niego poczekać, aby dowiedzieć się, kiedy zakończy się.

Trzeci przypadek to paskudny. Trzeci przypadek jest jak drugi przypadek, z tym wyjątkiem, że nawet nie odzyskasz zadania. Nie możesz wiedzieć, kiedy zadanie zakończyło się.

Przypadek asynchronicznej pustki to „odpal i zapomnij”: uruchamiasz łańcuch zadań, ale nie obchodzi cię, kiedy zostanie zakończony. Gdy funkcja powróci, wiesz tylko, że wykonano wszystko do pierwszego oczekiwania. Wszystko po pierwszym oczekiwaniu będzie działać w jakimś nieokreślonym punkcie w przyszłości, do którego nie masz dostępu.


6

Myślę, że możesz użyć go również async voiddo rozpoczęcia operacji w tle, o ile będziesz uważać na wyjątki. Myśli?

class Program {

    static bool isFinished = false;

    static void Main(string[] args) {

        // Kick off the background operation and don't care about when it completes
        BackgroundWork();

        Console.WriteLine("Press enter when you're ready to stop the background operation.");
        Console.ReadLine();
        isFinished = true;
    }

    // Using async void to kickoff a background operation that nobody wants to be notified about when it completes.
    static async void BackgroundWork() {
        // It's important to catch exceptions so we don't crash the appliation.
        try {
            // This operation will end after ten interations or when the app closes. Whichever happens first.
            for (var count = 1; count <= 10 && !isFinished; count++) {
                await Task.Delay(1000);
                Console.WriteLine($"{count} seconds of work elapsed.");
            }
            Console.WriteLine("Background operation came to an end.");
        } catch (Exception x) {
            Console.WriteLine("Caught exception:");
            Console.WriteLine(x.ToString());
        }
    }
}

1
Problem z wywoływaniem asynchronicznej nieważności polega na tym, że nawet nie
odzyskujesz

Ma to sens w przypadku (rzadkich) operacji w tle, w których nie zależy na wyniku i nie chcesz, aby wyjątek wpływał na inne operacje. Typowym przypadkiem użycia byłoby wysłanie zdarzenia dziennika do serwera dziennika. Chcesz, aby tak się działo w tle, i nie chcesz, aby twoja usługa / moduł obsługi żądań zawiódł, jeśli serwer dziennika był wyłączony. Oczywiście musisz wychwycić wszystkie wyjątki, w przeciwnym razie proces zostanie zakończony. Czy jest lepszy sposób na osiągnięcie tego?
Florian Winter,

Wyjątków od metody Async Void nie można złapać za pomocą Catch. msdn.microsoft.com/en-us/magazine/jj991977.aspx
Si Zi

3
@ SiZi catch jest WEWNĄTRZ metody asynchronicznej pustki, jak pokazano w przykładzie, i został złapany.
bboyle1234

Co się stanie, gdy zmienią się Twoje wymagania, aby nie wymagać wykonywania pracy, jeśli nie możesz tego zalogować - to musisz zmienić podpisy w całym interfejsie API, zamiast po prostu zmienić logikę, która obecnie ma // Ignoruj ​​wyjątek
Milney

0

Moja odpowiedź jest prosta: nie możesz oczekiwać nieważnej metody

Error   CS4008  Cannot await 'void' TestAsync   e:\test\TestAsync\TestAsyncProgram.cs

Więc jeśli metoda jest asynchroniczna, lepiej być wyczekiwanym, ponieważ możesz stracić przewagę asynchroniczną.


-2

Według dokumentacji Microsoft NIGDY nie należy używaćasync void

Nie rób tego: Poniższy przykład wykorzystuje, async voidco powoduje, że żądanie HTTP jest kompletne po osiągnięciu pierwszego oczekiwania:

  • Co ZAWSZE jest złą praktyką w aplikacjach ASP.NET Core.

  • Dostęp do odpowiedzi HttpResponse po zakończeniu żądania HTTP.

  • Awarie procesu.

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.