Poczekaj na void async method

155

Jak mogę czekać, aż void asyncmetoda zakończy swoją pracę?

na przykład mam funkcję jak poniżej:

async void LoadBlahBlah()
{
    await blah();
    ...
}

teraz chcę się upewnić, że wszystko zostało załadowane, zanim przejdę do innego miejsca.

MBZ
źródło

Odpowiedzi:

234

Najlepszą praktyką jest zaznaczanie funkcji async voidtylko wtedy, gdy jest to metoda odpal i zapomnij, jeśli chcesz poczekać, powinieneś oznaczyć ją jako async Task.

Jeśli nadal chcesz poczekać, zawiń to w ten sposób await Task.Run(() => blah())

Rohit Sharma
źródło
blah oczywiście zwraca zadanie i ma asynchroniczny podpis, dlaczego miałbyś to wywołać bez czekania. nawet VS da ci ostrzeżenie.
batmaci
8
await Task.Run(() => An_async_void_method_I_can_not_modify_now())
themefield
1
await Task.Run(() => blah())wprowadza w błąd. To nie czeka na zakończenie funkcji asynchronicznej blah, tylko czeka na (trywialne) utworzenie zadania i jest kontynuowane bezpośrednio przed blah()zakończeniem.
Jonathan Lidbeck
@JonathanLidbeck stwierdzenie "kontynuowane jest natychmiast, zanim blah jest błędne. Spróbuj" await Task.Run (() => Thread.Sleep (10_000)) ", zadanie jest oczekiwane przez 10 sekund + przed wykonaniem następnej linii
Rohit Sharma
@RohitSharma, to jest poprawne dla twojego przykładu, ale Thread.Sleepnie jest asynchroniczne. To pytanie dotyczy oczekiwania na async voidfunkcję, powiedzmyasync void blah() { Task.Delay(10000); }
Jonathan Lidbeck
59

Jeśli możesz zmienić podpis swojej funkcji na async Task, możesz użyć przedstawionego tutaj kodu

Społeczność
źródło
27

Najlepszym rozwiązaniem jest użycie async Task. Należy tego unikać async voidz kilku powodów, z których jednym jest składalność.

Jeśli metoda nie może powrócić Task(np. Jest to program obsługi zdarzenia), możesz użyć SemaphoreSlimsygnału metody, gdy ma zamiar zakończyć. Rozważ zrobienie tego w finallybloku.

Stephen Cleary
źródło
1

wykonaj AutoResetEvent, wywołaj funkcję, a następnie poczekaj na AutoResetEvent, a następnie ustaw ją w async void, gdy wiesz, że jest wykonana.

Możesz także poczekać na zadanie, które wróci z Twojej pustej asynchronicznej

user2712345
źródło
1

Naprawdę nie musisz nic robić ręcznie, awaitsłowo kluczowe wstrzymuje wykonywanie funkcji do czasu blah()powrotu.

private async void SomeFunction()
{
     var x = await LoadBlahBlah(); <- Function is not paused
     //rest of the code get's executed even if LoadBlahBlah() is still executing
}

private async Task<T> LoadBlahBlah()
{
     await DoStuff();  <- function is paused
     await DoMoreStuff();
}

Tjest typem blah()zwracanego obiektu

Nie można naprawdę funkcja więc może nie byćawaitvoidLoadBlahBlah()void

Mayank
źródło
Chcę poczekać na LoadBlahBlah()zakończenie, a nieblah()
MBZ
10
Niestety asynchroniczny void metody nie wstrzymać wykonanie (w przeciwieństwie do asynchronicznej metody zadania)
ghord
-1

Wiem, że to stare pytanie, ale wciąż jest to problem, do którego wciąż wkraczam, a mimo to nadal nie ma jasnego rozwiązania, aby zrobić to poprawnie, używając async / await w metodzie async void signature.

Jednak zauważyłem, że .Wait () działa poprawnie wewnątrz metody void.

a ponieważ async void i void mają ten sam podpis, może być konieczne wykonanie następujących czynności.

void LoadBlahBlah()
{
    blah().Wait(); //this blocks
}

Dość myląco async / await nie blokuje następnego kodu.

async void LoadBlahBlah()
{
    await blah(); //this does not block
}

Kiedy dekompilujesz swój kod, przypuszczam, że async void tworzy wewnętrzne zadanie (podobnie jak async Task), ale ponieważ podpis nie obsługuje zwracania tych wewnętrznych zadań

oznacza to, że wewnętrznie metoda async void nadal będzie mogła „oczekiwać” wewnętrznie asynchronicznych metod. ale zewnętrznie nie jest w stanie wiedzieć, kiedy zadanie wewnętrzne jest zakończone.

Mój wniosek jest taki, że async void działa zgodnie z przeznaczeniem, a jeśli potrzebujesz informacji zwrotnej z wewnętrznego zadania, musisz zamiast tego użyć podpisu async Task.

mam nadzieję, że moje wędrówki mają sens dla każdego, kto szuka odpowiedzi.

Edycja: stworzyłem przykładowy kod i zdekompilowałem go, aby zobaczyć, co się właściwie dzieje.

static async void Test()
{
    await Task.Delay(5000);
}

static async Task TestAsync()
{
    await Task.Delay(5000);
}

Zamienia się w (edytuj: wiem, że kod ciała nie jest tutaj, ale w statemachines, ale statemachines był w zasadzie identyczny, więc nie zawracałem sobie głowy dodawaniem ich)

private static void Test()
{
    <Test>d__1 stateMachine = new <Test>d__1();
    stateMachine.<>t__builder = AsyncVoidMethodBuilder.Create();
    stateMachine.<>1__state = -1;
    AsyncVoidMethodBuilder <>t__builder = stateMachine.<>t__builder;
    <>t__builder.Start(ref stateMachine);
}
private static Task TestAsync()
{
    <TestAsync>d__2 stateMachine = new <TestAsync>d__2();
    stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create();
    stateMachine.<>1__state = -1;
    AsyncTaskMethodBuilder <>t__builder = stateMachine.<>t__builder;
    <>t__builder.Start(ref stateMachine);
    return stateMachine.<>t__builder.Task;
}

ani AsyncVoidMethodBuilder, ani AsyncTaskMethodBuilder w rzeczywistości nie mają żadnego kodu w metodzie Start, który wskazywałby na ich blokowanie i zawsze byłyby uruchamiane asynchronicznie po ich uruchomieniu.

co oznacza, że ​​bez zwracającego zadania nie byłoby możliwości sprawdzenia, czy zostało ono zakończone.

zgodnie z oczekiwaniami uruchamia tylko zadanie działające asynchronicznie, a następnie kontynuuje je w kodzie. i zadanie asynchroniczne, najpierw uruchamia zadanie, a następnie zwraca je.

więc myślę, że moją odpowiedzią byłoby nigdy nie używać async void, jeśli chcesz wiedzieć, kiedy zadanie jest wykonane, do tego służy Async Task.

Droa
źródło