Startu nie można wywołać w przypadku zadania w stylu obietnicy. wyjątek nadchodzi

109

Tworzę prostą aplikację desktopową wpf. Interfejs użytkownika ma tylko przycisk i kod w pliku .cs, np.

private void Button_Click_2(object sender, RoutedEventArgs e)
{
    FunctionA();
}

public void FunctionA()
{
    Task.Delay(5000).Start();
    MessageBox.Show("Waiting Complete");
}

Ale, zaskakująco, linia Task.Delay(5000).Start();rzuca InvalidOperationException:

Startu nie można wywołać w przypadku zadania w stylu obietnicy.

Czy ktoś może pomóc, dlaczego tak jest?

DJ
źródło

Odpowiedzi:

171

Otrzymujesz ten błąd, ponieważ Taskklasa już rozpoczęła zadanie przed przekazaniem go tobie. Powinieneś zawsze wywoływać Startzadanie, które tworzysz, wywołując jego konstruktor, a nawet nie powinieneś tego robić, chyba że masz ważny powód, aby nie rozpoczynać zadania podczas jego tworzenia; jeśli chcesz, aby zaczął się od razu, użyj Task.Runlub, Task.Factory.StartNewaby utworzyć i rozpocząć nowy Task.

Więc teraz wiemy, jak pozbyć się tego brzydkiego Start. Uruchomisz swój kod i zauważysz, że okno komunikatu jest wyświetlane od razu, a nie 5 sekund później, o co chodzi?

Cóż, Task.Delaypo prostu daje ci zadanie, które zostanie ukończone za 5 sekund. Nie przerywa wykonywania wątku przez 5 sekund. To, co chcesz zrobić, to mieć kod, który zostanie wykonany po zakończeniu tego zadania. Po to ContinueWithjest. Pozwala uruchomić kod po wykonaniu danego zadania:

public void FunctionA()
{
    Task.Delay(5000)
    .ContinueWith(t => 
    {
        MessageBox.Show("Waiting Complete");
    });
}

Będzie to działać zgodnie z oczekiwaniami.

Moglibyśmy również wykorzystać awaitsłowo kluczowe C # 5.0, aby łatwiej dodawać kontynuacje:

public async Task FunctionA()
{
    await Task.Delay(5000);
    MessageBox.Show("Waiting Complete");
}

Chociaż pełne wyjaśnienie tego, co się tutaj dzieje, wykracza poza zakres tego pytania, efektem końcowym jest metoda, która zachowuje się bardzo podobnie do poprzedniej metody; pokaże okno komunikatu 5 sekund po wywołaniu metody, ale sama metoda zwróci [prawie] od razu w obu przypadkach. To powiedziawszy, awaitjest bardzo potężny i pozwala nam pisać metody, które wydają się proste i nieskomplikowane, ale byłoby o wiele trudniejsze i trudniejsze do napisania ContinueWithbezpośrednio przy użyciu . Znacznie upraszcza również obsługę błędów, usuwając wiele standardowych kodów.

Servy
źródło
1

Spróbuj tego.

private void Button_Click_2(object sender, RoutedEventArgs e)
{
    FunctionA();
}

public async void FunctionA()
{
    await Task.Delay(5000);
    MessageBox.Show("Waiting Complete");
}
Mathias Lykkegaard Lorenzen
źródło
-4

Jak powiedział Servy, zadanie już się rozpoczęło, więc pozostaje tylko na nie czekać (.Wait ()):

private void Button_Click_2(object sender, RoutedEventArgs e)
{
    FunctionA();
}
public void FunctionA()
{
    Task.Delay(5000).Wait();
    MessageBox.Show("Waiting Complete");
}
Sergiu
źródło
1
Wywołanie Wait()zadania spowoduje zablokowanie bieżącego wątku do czasu rozwiązania zadania. Prawie nigdy tego nie chcesz.
Jeremy,
1
@Jeremy: Rzeczywiście, warto zwrócić uwagę na zachowanie, o którym wspomniałeś, ale w tym przypadku jego FunctionA już blokował bieżący wątek, więc założyłem, że szuka sposobu na określenie, kiedy zadanie zostało ukończone. Aby wyjaśnić różnicę między Wait i async (dla przyszłych czytelników), przeczytaj ten link
Sergiu,