Suprimiendo "advertencia CS4014: debido a que no se espera esta llamada, la ejecución del método actual continúa ..."

156

Este no es un duplicado de "Cómo llamar de forma segura a un método asíncrono en C # sin esperar" .

¿Cómo suprimo bien la siguiente advertencia?

advertencia CS4014: debido a que no se espera esta llamada, la ejecución del método actual continúa antes de que se complete la llamada. Considere aplicar el operador 'esperar' al resultado de la llamada.

Un simple ejemplo:

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); 
}

Lo que probé y no me gustó:

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
    // ...
}

Actualizado , dado que la respuesta original aceptada ha sido editada, he cambiado la respuesta aceptada a la que usa los descartes de C # 7.0 , ya que no creo que ContinueWithsea ​​apropiado aquí. Siempre que necesito registrar excepciones para operaciones de disparar y olvidar, utilizo un enfoque más elaborado propuesto por Stephen Cleary aquí .

noseratio
fuente
1
Entonces, ¿crees #pragmaque no es agradable?
Frédéric Hamidi
10
@ FrédéricHamidi, lo hago.
noseratio
2
@Noseratio: Ah, cierto. Lo siento, pensé que era la otra advertencia. ¡Ignorame!
Jon Skeet
3
@Terribad: No estoy realmente seguro, parece que la advertencia es bastante razonable en la mayoría de los casos. En particular, se debe pensar en lo que queremos que suceda, las averías - por lo general, incluso para "dispara y olvida" que debe encontrar la manera de conectarse fallos etc.
Jon Skeet
44
@Terribad, antes de usarlo de esta forma u otra, debe tener una idea clara de cómo se propagan las excepciones para los métodos asíncronos (verifique esto ). Luego, la respuesta de @ Knaģis proporciona una forma elegante de no perder ninguna excepción para disparar y olvidar, a través de un async voidmétodo auxiliar.
noseratio

Respuestas:

160

Con C # 7 ahora puede usar descartes :

_ = WorkAsync();
Anthony Wieser
fuente
77
Esta es una pequeña y útil función de lenguaje que simplemente no puedo recordar. Es como si hubiera un _ = ...cerebro en mi cerebro.
Marc L.
3
Encontré un SupressMessage que eliminó mi advertencia de mi "Lista de errores" de Visual Studio pero no de "Salida" y #pragma warning disable CSxxxxparece más fea que el descarte;)
David Savage
122

Puede crear un método de extensión que evitará la advertencia. El método de extensión puede estar vacío o puede agregar manejo de excepciones .ContinueWith()allí.

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();
}

Sin embargo, ASP.NET cuenta el número de tareas en ejecución, por lo que no funcionará con la Forget()extensión simple como se enumeró anteriormente y en su lugar puede fallar con la excepción:

Un módulo o controlador asincrónico completado mientras una operación asincrónica aún estaba pendiente.

Con .NET 4.5.2 se puede resolver usando 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
fuente
8
Me he encontrado TplExtensions.Forget. Hay mucha más bondad debajo Microsoft.VisualStudio.Threading. Desearía que estuviera disponible para su uso fuera del SDK de Visual Studio.
noseratio
1
@Noseratio y Knagis, me gusta este enfoque y planeo usarlo. Publiqué una pregunta de seguimiento relacionada: stackoverflow.com/questions/22864367/fire-and-forget-approach
Matt Smith
3
@stricq ¿Para qué serviría agregar ConfigureAwait (false) a Forget ()? Según tengo entendido, ConfigureAwait solo afecta a la sincronización de subprocesos en el punto donde se usa waitit en una tarea, pero el propósito de Forget () es tirar la tarea, por lo que la tarea nunca se puede esperar, por lo que ConfigureAwait aquí no tiene sentido.
dthorpe
3
Si el hilo de desove desaparece antes de que se complete la tarea de fuego y olvido, sin ConfigureAwait (falso) aún intentará volver a colocarse en el hilo de desove, ese hilo desaparece para que se bloquee. La configuración de ConfigureAwait (false) le dice al sistema que no vuelva a ordenar al hilo de llamada.
stricq
2
Esta respuesta tiene una edición para administrar un caso específico, más una docena de comentarios. Simplemente las cosas son a menudo las correctas, ¡descarta! Y cito la respuesta @ fjch1997: es estúpido crear un método que requiera unos pocos ticks más para ejecutarse, solo con el propósito de suprimir una advertencia.
Teejay
39

Puede decorar el método con el siguiente atributo:

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

Básicamente le está diciendo al compilador que sabe lo que está haciendo y que no necesita preocuparse por posibles errores.

La parte importante de este código es el segundo parámetro. La parte "CS4014:" es lo que suprime la advertencia. Puedes escribir lo que quieras sobre el resto.

Fábio
fuente
No funciona para mí: Visual Studio para Mac 7.0.1 (compilación 24). Parece que debería pero, no.
IronRod
1
[SuppressMessage("Compiler", "CS4014")]suprime el mensaje en la ventana Lista de errores, pero la ventana Salida aún muestra una línea de advertencia
David Ching
35

Mis dos formas de lidiar con esto.

Guárdelo en una variable de descarte (C # 7)

Ejemplo

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

Desde la introducción de los descartes en C # 7, ahora considero que esto es mejor que suprimir la advertencia. Porque no solo suprime la advertencia, sino que también deja clara la intención de disparar y olvidar.

Además, el compilador podrá optimizarlo en modo de lanzamiento.

Solo suprímalo

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

es una buena solución para "disparar y olvidar".

La razón por la que existe esta advertencia es porque, en muchos casos, no es su intención utilizar un método que devuelva la tarea sin esperarla. Suprimir la advertencia cuando tiene la intención de disparar y olvidar tiene sentido.

Si tiene problemas para recordar cómo se deletrea #pragma warning disable 4014, simplemente deje que Visual Studio lo agregue por usted. Presione Ctrl +. para abrir "Acciones rápidas" y luego "Suprimir CS2014"

Considerándolo todo

Es estúpido crear un método que requiera unos cuantos ticks más para ejecutarse, solo con el propósito de suprimir una advertencia.

fjch1997
fuente
Esto funcionó en Visual Studio para Mac 7.0.1 (compilación 24).
IronRod
1
Es estúpido crear un método que requiera unos pocos ticks más para ejecutar, solo con el propósito de suprimir una advertencia : este no agrega tics adicionales y la OMI es más legible:[MethodImpl(MethodImplOptions.AggressiveInlining)] void Forget(this Task @this) { } /* ... */ obj.WorkAsync().Forget();
noseratio
1
@Noseratio Muchas veces cuando uso AggressiveInliningel compilador simplemente lo ignora por cualquier razón
fjch1997
1
Me gusta la opción pragma, porque es súper simple y solo se aplica a la línea (o sección) actual de código, no a un método completo.
wasatchwizard
2
No olvides usar el código de error como #pragma warning disable 4014y luego restaurar la advertencia con #pragma warning restore 4014. Todavía funciona sin el código de error, pero si no agrega el número de error, suprimirá todos los mensajes.
DunningKrugerEffect
11

Una manera fácil de detener la advertencia es simplemente asignar la Tarea al llamarla:

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

Y así en tu publicación original harías:

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
fuente
Mencioné este enfoque en la pregunta en sí, como uno de los que no me gustó particularmente.
noseratio
Whoops! No lo noté porque estaba en la misma sección de código que tu pragma ... Y estaba buscando respuestas. Aparte de eso, ¿qué es lo que no te gusta de este método?
noelicus
1
No me gusta que taskparezca una variable local olvidada. Casi como el compilador debería darme otra advertencia, algo así como " taskse asigna pero su valor nunca se usa", además de que no lo hace. Además, hace que el código sea menos legible. Yo mismo uso este enfoque.
noseratio
Es justo: tuve una sensación similar, por eso lo llamo fireAndForget... así que espero que de ahora en adelante no tenga referencias.
noelicus
4

El motivo de la advertencia es que WorkAsync está devolviendo un mensaje Taskque nunca se lee ni se espera. Puede establecer el tipo de retorno de WorkAsync voidy la advertencia desaparecerá.

Normalmente, un método devuelve un Taskcuando la persona que llama necesita saber el estado del trabajador. En el caso de disparar y olvidar, se debe devolver el vacío para que parezca que la persona que llama es independiente del método llamado.

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); 
}
Víctor
fuente
2

¿Por qué no envolverlo dentro de un método asíncrono que devuelve vacío? Un poco largo pero se usan todas las variables.

static async Task StartWorkAsync()
{   
     async void WorkAndForgetAsync() => await WorkAsync();
     WorkAndForgetAsync(); // no warning
}
Akli
fuente
1

Encontré este enfoque por accidente hoy. Puede definir un delegado y asignar primero el método asíncrono al delegado.

    delegate Task IntermediateHandler();



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

y lo llaman así

(new IntermediateHandler(AsyncOperation))();

...

Pensé que era interesante que el compilador no diera exactamente la misma advertencia al usar el delegado.

David Beavon
fuente
No es necesario declarar un delegado, también podría hacerlo, (new Func<Task>(AsyncOperation))()aunque IMO todavía es demasiado detallado.
noseratio