Pomijanie „ostrzeżenia CS4014: ponieważ to wywołanie nie jest oczekiwane, wykonywanie bieżącej metody jest kontynuowane…”

156

To nie jest duplikat „Jak bezpiecznie wywołać metodę asynchroniczną w języku C # bez oczekiwania” .

Jak ładnie ukryć poniższe ostrzeżenie?

ostrzeżenie CS4014: Ponieważ to wywołanie nie jest oczekiwane, wykonywanie bieżącej metody jest kontynuowane przed zakończeniem wywołania. Rozważ zastosowanie operatora „await” do wyniku wywołania.

Prosty przykład:

static async Task WorkAsync()
{
    await Task.Delay(1000);
    Console.WriteLine("Done!");
}

static async Task StartWorkAsync()
{
    WorkAsync(); // I want fire-and-forget 

    // more unrelated async/await stuff here, e.g.:
    // ...
    await Task.Delay(2000); 
}

Co próbowałem i nie podobało się:

static async Task StartWorkAsync()
{
    #pragma warning disable 4014
    WorkAsync(); // I want fire-and-forget here
    #pragma warning restore 4014
    // ...
}

static async Task StartWorkAsync()
{
    var ignoreMe = WorkAsync(); // I want fire-and-forget here
    // ...
}

Zaktualizowano , ponieważ pierwotnie zaakceptowana odpowiedź została edytowana, zmieniłem zaakceptowaną odpowiedź na tę, która używa odrzuconych C # 7.0 , ponieważ uważam, że nie ContinueWithjest tu odpowiednia. Ilekroć muszę rejestrować wyjątki dla operacji „wystrzel i zapomnij”, używam bardziej złożonego podejścia zaproponowanego przez Stephena Cleary'ego .

noseratio
źródło
1
Więc myślisz, że #pragmato nie jest miłe?
Frédéric Hamidi
10
@ FrédéricHamidi, tak.
noseratio
2
@Noseratio: Ach, racja. Przepraszam, myślałem, że to drugie ostrzeżenie. Ignoruj ​​mnie!
Jon Skeet,
3
@Terribad: Nie jestem do końca pewien - wydaje się, że ostrzeżenie jest całkiem rozsądne w większości przypadków. W szczególności powinieneś pomyśleć o tym, co chcesz, aby stało się z jakimikolwiek awariami - zwykle nawet w przypadku „odpal i zapomnij” powinieneś dowiedzieć się, jak rejestrować awarie itp.
Jon Skeet
4
@Terribad, zanim użyjesz go w ten czy inny sposób, powinieneś mieć jasny obraz tego, jak propagowane są wyjątki dla metod asynchronicznych (sprawdź to ). Następnie odpowiedź @ Knaģis zapewnia elegancki sposób, aby nie stracić żadnych wyjątków dla „wystrzel i zapomnij”, za pomocą async voidmetody pomocniczej.
noseratio

Odpowiedzi:

160

Dzięki C # 7 możesz teraz używać odrzutów :

_ = WorkAsync();
Anthony Wieser
źródło
7
To przydatna mała funkcja językowa, której po prostu nie pamiętam. To tak, jakby _ = ...w moim mózgu.
Marc L.
3
Znalazłem SupressMessage, który usunął moje ostrzeżenie z mojej listy błędów programu Visual Studio, ale nie z pozycji „Wyjście” i #pragma warning disable CSxxxxwygląda bardziej brzydko niż odrzucenie;)
David Savage
122

Możesz utworzyć metodę rozszerzenia, która zapobiegnie pojawieniu się ostrzeżenia. Metoda rozszerzenia może być pusta lub można tam dodać obsługę wyjątków .ContinueWith().

static class TaskExtensions
{
    public static void Forget(this Task task)
    {
        task.ContinueWith(
            t => { WriteLog(t.Exception); },
            TaskContinuationOptions.OnlyOnFaulted);
    }
}

public async Task StartWorkAsync()
{
    this.WorkAsync().Forget();
}

Jednak ASP.NET liczy liczbę uruchomionych zadań, więc nie będzie działać z prostym Forget()rozszerzeniem wymienionym powyżej i zamiast tego może się nie powieść z wyjątkiem:

Asynchroniczny moduł lub procedura obsługi została zakończona, gdy operacja asynchroniczna nadal była w toku.

W przypadku .NET 4.5.2 można to rozwiązać za pomocą HostingEnvironment.QueueBackgroundWorkItem:

public static Task HandleFault(this Task task, CancellationToken cancelToken)
{
    return task.ContinueWith(
        t => { WriteLog(t.Exception); },
        cancelToken,
        TaskContinuationOptions.OnlyOnFaulted,
        TaskScheduler.Default);
}

public async Task StartWorkAsync()
{
    System.Web.Hosting.HostingEnvironment.QueueBackgroundWorkItem(
        cancelToken => this.WorkAsync().HandleFault(cancelToken));
}
Knaģis
źródło
8
Znalazłem TplExtensions.Forget. Pod spodem jest o wiele więcej dobra Microsoft.VisualStudio.Threading. Żałuję, że nie udostępniono go do użytku poza pakietem Visual Studio SDK.
noseratio
1
@Noseratio i Knagis, podoba mi się to podejście i planuję z niego skorzystać. Opublikowałem powiązane pytanie uzupełniające: stackoverflow.com/questions/22864367/fire-and-forget-approach
Matt Smith
3
@stricq Jaki cel miałoby służyć dodanie ConfigureAwait (false) do Forget ()? Jak rozumiem, ConfigureAwait wpływa tylko na synchronizację wątków w punkcie, w którym await jest używany w zadaniu, ale celem Forget () jest wyrzucenie zadania, więc zadanie nie może być nigdy oczekiwane, więc ConfigureAwait tutaj jest bezcelowe.
dthorpe
3
Jeśli spawnujący wątek zniknie przed zakończeniem zadania `` uruchom i zapomnij '', bez ConfigureAwait (false) będzie nadal próbował skierować się z powrotem do spawnującego wątku, ten wątek zniknie, więc zablokuje się. Ustawienie ConfigureAwait (false) informuje system, aby nie kierował z powrotem do wątku wywołującego.
stricq
2
Ta odpowiedź zawiera edycję dotyczącą zarządzania konkretną sprawą oraz kilkanaście komentarzy. Po prostu rzeczy są często właściwe, idź na odrzuty! Cytuję odpowiedź @ fjch1997: głupotą jest tworzenie metody, której wykonanie wymaga jeszcze kilku tików, tylko w celu stłumienia ostrzeżenia.
Teejay
39

Możesz ozdobić metodę następującym atrybutem:

[System.Diagnostics.CodeAnalysis.SuppressMessage("Await.Warning", "CS4014:Await.Warning")]
static async Task StartWorkAsync()
{
    WorkAsync();
    // ...
}

Zasadniczo mówisz kompilatorowi, że wiesz, co robisz i nie musisz się martwić o możliwy błąd.

Ważną częścią tego kodu jest drugi parametr. Część „CS4014:” pomija ostrzeżenie. W pozostałej części możesz napisać, co chcesz.

Fábio
źródło
Nie działa dla mnie: Visual Studio dla komputerów Mac 7.0.1 (kompilacja 24). Wydaje się, że powinno, ale - nie.
IronRod
1
[SuppressMessage("Compiler", "CS4014")]pomija komunikat w oknie Lista błędów, ale okno Wyjście nadal wyświetla wiersz ostrzeżenia
David Ching
35

Moje dwa sposoby radzenia sobie z tym.

Zapisz go w zmiennej discard (C # 7)

Przykład

_ = Task.Run(() => DoMyStuff()).ConfigureAwait(false);

Od czasu wprowadzenia odrzutów w C # 7, teraz uważam, że jest to lepsze niż wyłączenie ostrzeżenia. Ponieważ nie tylko tłumi ostrzeżenie, ale także sprawia, że ​​zamiar ognia i zapomnienia jest jasny.

Ponadto kompilator będzie mógł go zoptymalizować w trybie wydania.

Po prostu stłum to

#pragma warning disable 4014
...
#pragma warning restore 4014

jest wystarczająco dobrym rozwiązaniem, aby „odpalić i zapomnieć”.

Powodem, dla którego istnieje to ostrzeżenie, jest to, że w wielu przypadkach nie jest Twoim zamiarem użycie metody, która zwraca zadanie bez czekania na to. Powstrzymywanie ostrzeżenia, gdy zamierzasz strzelać i zapomnieć, ma sens.

Jeśli masz problemy z zapamiętaniem pisowni #pragma warning disable 4014, po prostu pozwól programowi Visual Studio dodać to za Ciebie. Naciśnij Ctrl +. aby otworzyć „Szybkie akcje”, a następnie „Pomiń CS2014”

W sumie

Głupotą jest tworzenie metody, której wykonanie wymaga jeszcze kilku tików, tylko w celu zniesienia ostrzeżenia.

fjch1997
źródło
Działało to w programie Visual Studio dla komputerów Mac 7.0.1 (kompilacja 24).
IronRod
1
Głupio jest tworzyć metodę, której wykonanie wymaga jeszcze kilku tików, tylko w celu zniesienia ostrzeżenia - ten w ogóle nie dodaje dodatkowych tików, a IMO jest bardziej czytelny:[MethodImpl(MethodImplOptions.AggressiveInlining)] void Forget(this Task @this) { } /* ... */ obj.WorkAsync().Forget();
noseratio
1
@Noseratio Wiele razy, kiedy używam AggressiveInliningkompilatora, po prostu go ignoruje z dowolnego powodu
fjch1997
1
Podoba mi się opcja pragma, ponieważ jest bardzo prosta i dotyczy tylko bieżącego wiersza (lub sekcji) kodu, a nie całej metody.
wasatchwizard
2
Nie zapomnij użyć kodu błędu, takiego jak, #pragma warning disable 4014a następnie przywrócić ostrzeżenie za pomocą #pragma warning restore 4014. Nadal działa bez kodu błędu, ale jeśli nie dodasz numeru błędu, pominie wszystkie komunikaty.
DunningKrugerEffect
11

Łatwym sposobem na zatrzymanie ostrzeżenia jest po prostu przypisanie zadania podczas jego wywoływania:

Task fireAndForget = WorkAsync(); // No warning now

I tak w swoim oryginalnym poście zrobiłbyś:

static async Task StartWorkAsync()
{
    // Fire and forget
    var fireAndForget = WorkAsync(); // Tell the compiler you know it's a task that's being returned 

    // more unrelated async/await stuff here, e.g.:
    // ...
    await Task.Delay(2000); 
}
noelicus
źródło
Wspomniałem o tym podejściu w samym pytaniu, jako o jednym z tych, które szczególnie mi się nie podobały.
noseratio
Ups! Nie zauważyłem tego, ponieważ było to w tej samej sekcji kodu, co twoja pragma ... A ja szukałem odpowiedzi. Poza tym, co ci się nie podoba w tej metodzie?
noelicus
1
Nie podoba mi się to, że taskwygląda jak zapomniana zmienna lokalna. Prawie tak, jak kompilator powinien dać mi kolejne ostrzeżenie, coś w stylu „ taskjest przypisane, ale jego wartość nigdy nie jest używana”, poza tym nie. Ponadto sprawia, że ​​kod jest mniej czytelny. Sam stosuję to podejście.
noseratio
W porządku - miałem podobne uczucie, dlatego nazywam to fireAndForget... więc spodziewam się, że odtąd nie będzie miało odniesienia.
noelicus
4

Przyczyną ostrzeżenia jest to, Taskże WorkAsync zwraca komunikat, który nigdy nie jest odczytywany ani oczekiwany. Możesz ustawić zwracany typ WorkAsync na, voida ostrzeżenie zniknie.

Zazwyczaj metoda zwraca a, Taskgdy wywołujący musi znać status procesu roboczego. W przypadku odpalenia i zapomnienia, void powinien zostać zwrócony, aby przypominał, że wywołujący jest niezależny od wywoływanej metody.

static async void WorkAsync()
{
    await Task.Delay(1000);
    Console.WriteLine("Done!");
}

static async Task StartWorkAsync()
{
    WorkAsync(); // no warning since return type is void

    // more unrelated async/await stuff here, e.g.:
    // ...
    await Task.Delay(2000); 
}
Zwycięzca
źródło
2

Dlaczego nie zawinąć go w metodę asynchroniczną, która zwraca wartość void? Trochę za długie, ale używane są wszystkie zmienne.

static async Task StartWorkAsync()
{   
     async void WorkAndForgetAsync() => await WorkAsync();
     WorkAndForgetAsync(); // no warning
}
Akli
źródło
1

Dziś znalazłem to podejście przez przypadek. Możesz zdefiniować delegata i najpierw przypisać metodę asynchroniczną do delegata.

    delegate Task IntermediateHandler();



    static async Task AsyncOperation()
    {
        await Task.Yield();
    }

i tak to nazwij

(new IntermediateHandler(AsyncOperation))();

...

Pomyślałem, że to interesujące, że kompilator nie dałby dokładnie tego samego ostrzeżenia podczas korzystania z delegata.

David Beavon
źródło
Nie musisz deklarować delegata, równie dobrze możesz to zrobić, (new Func<Task>(AsyncOperation))()chociaż IMO nadal jest zbyt szczegółowe.
noseratio