advirtiendo que no se espera esta llamada, la ejecución del método actual continúa


135

Acabo de recibir VS2012 y estoy tratando de manejarlo async.

Digamos que tengo un método que obtiene algún valor de una fuente de bloqueo. No quiero bloquear la persona que llama del método. Podría escribir el método para tomar una devolución de llamada que se invoca cuando llega el valor, pero como estoy usando C # 5, decido hacer que el método sea asíncrono para que las personas que llaman no tengan que lidiar con devoluciones de llamada:

// contrived example (edited in response to Servy's comment)
public static Task<string> PromptForStringAsync(string prompt)
{
    return Task.Factory.StartNew(() => {
        Console.Write(prompt);
        return Console.ReadLine();
    });
}

Aquí hay un método de ejemplo que lo llama. Si PromptForStringAsyncno fuera asíncrono, este método requeriría anidar una devolución de llamada dentro de una devolución de llamada. Con async, puedo escribir mi método de esta manera muy natural:

public static async Task GetNameAsync()
{
    string firstname = await PromptForStringAsync("Enter your first name: ");
    Console.WriteLine("Welcome {0}.", firstname);

    string lastname = await PromptForStringAsync("Enter your last name: ");
    Console.WriteLine("Name saved as '{0} {1}'.", firstname, lastname);
}

Hasta aquí todo bien. El problema es cuando llamo a GetNameAsync:

public static void DoStuff()
{
    GetNameAsync();
    MainWorkOfApplicationIDontWantBlocked();
}

El punto GetNameAsynces que es asíncrono. No quiero que se bloquee, porque quiero volver a MainWorkOfApplicationIDontWantBlocked lo antes posible y dejar que GetNameAsync haga lo suyo en segundo plano. Sin embargo, llamarlo de esta manera me da una advertencia del compilador en la GetNameAsynclínea:

Warning 1   Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Soy perfectamente consciente de que "la ejecución del método actual continúa antes de que se complete la llamada". Ese es el punto del código asincrónico, ¿verdad?

Prefiero compilar mi código sin advertencias, pero no hay nada que "arreglar" aquí porque el código está haciendo exactamente lo que tengo la intención de hacer. Puedo deshacerme de la advertencia almacenando el valor de retorno de GetNameAsync:

public static void DoStuff()
{
    var result = GetNameAsync(); // supress warning
    MainWorkOfApplicationIDontWantBlocked();
}

Pero ahora tengo un código superfluo. Visual Studio parece comprender que me vi obligado a escribir este código innecesario, ya que suprime la advertencia normal de "valor nunca utilizado".

También puedo deshacerme de la advertencia envolviendo GetNameAsync en un método que no es asíncrono:

    public static Task GetNameWrapper()
    {
        return GetNameAsync();
    }

Pero ese es un código aún más superfluo. Entonces tengo que escribir código que no necesito o tolerar una advertencia innecesaria.

¿Hay algo sobre mi uso de async que esté mal aquí?


2
Por cierto, al implementarlo PromptForStringAsync, hace más trabajo del que necesita; solo devuelve el resultado de Task.Factory.StartNew. Ya es una tarea cuyo valor es la cadena ingresada en la consola. No hay necesidad de esperar y devolver el resultado; hacerlo no agrega ningún valor nuevo.
Servy

Wwouldn't tiene más sentido para GetNameAsyncproporcionar el nombre completo que fue proporcionado por el usuario (es decir Task<Name>, en lugar de devolver un Task? DoStuffEntonces podría almacenar esa tarea, y ya sea awaitque después el otro método, o incluso pasar la tarea a la otra método por lo que podría awaito Waiten algún lugar dentro de ella de aplicación.
Servy

@Servy: si solo devuelvo la Tarea, aparece un error "Dado que este es un método asíncrono, la expresión de retorno debe ser del tipo 'cadena' en lugar de 'Tarea <cadena>'".
Barro

1
Eliminar la asyncpalabra clave.
Servy

13
En mi opinión, esta fue una mala elección para una advertencia por parte del equipo de C #. Las advertencias deben ser para cosas que casi seguramente están mal. Hay muchos casos en los que desea "disparar y olvidar" un método asíncrono, y otras veces en que realmente desea esperarlo.
MgSam

Respuestas:


103

Si realmente no necesita el resultado, simplemente puede cambiar la GetNameAsyncfirma para devolver void:

public static async void GetNameAsync()
{
    ...
}

Considere ver la respuesta a una pregunta relacionada: ¿Cuál es la diferencia entre devolver vacío y devolver una Tarea?

Actualizar

Si necesita el resultado, puede cambiar el GetNameAsyncpara devolver, por ejemplo Task<string>:

public static async Task<string> GetNameAsync()
{
    string firstname = await PromptForStringAsync("Enter your first name: ");
    string lastname = await PromptForStringAsync("Enter your last name: ");
    return firstname + lastname;
}

Y úsalo de la siguiente manera:

public static void DoStuff()
{
    Task<string> task = GetNameAsync();

    // Set up a continuation BEFORE MainWorkOfApplicationIDontWantBlocked
    Task anotherTask = task.ContinueWith(r => {
            Console.WriteLine(r.Result);
        });

    MainWorkOfApplicationIDontWantBlocked();

    // OR wait for the result AFTER
    string result = task.Result;
}

15
Ese solo sería el caso si a él nunca le importa el resultado, en lugar de no necesitar el resultado esta vez .
Servy

3
@Servy, cierto, gracias por aclararme. Pero los OP GetNameAsyncno devuelven ningún valor (excepto el resultado en sí mismo, por supuesto).
Nikolay Khil

2
Correcto, pero al devolver una tarea puede saber cuándo termina de ejecutar todas las operaciones asíncronas. Si regresa void, no tiene forma de saber cuándo está hecho. A eso me refería cuando dije "el resultado" en mi comentario anterior.
Servy

29
Como regla general, nunca debe tener un async voidmétodo, excepto los controladores de eventos.
Daniel Mann

2
Otra cosa a tener en cuenta aquí es que el comportamiento es diferente cuando se trata de excepciones no observadas cuando cambia a async voidcualquier excepción que no detecte bloqueará su proceso, pero en .net 4.5 seguirá ejecutándose.
Caleb Vear

62

Llego bastante tarde a esta discusión, pero también existe la opción de usar la #pragmadirectiva de preprocesador. Tengo un código asíncrono aquí y allá que explícitamente no quiero esperar en algunas condiciones, y no me gustan las advertencias y las variables no utilizadas como el resto de ustedes:

#pragma warning disable 4014
SomeMethodAsync();
#pragma warning restore 4014

El "4014"proviene de esta página de MSDN: Advertencia del compilador (nivel 1) CS4014 .

Vea también la advertencia / respuesta de @ ryan-horath aquí https://stackoverflow.com/a/12145047/928483 .

Se perderán las excepciones lanzadas durante una llamada asincrónica que no se espera. Para deshacerse de esta advertencia, debe asignar el valor de retorno de la tarea de la llamada asincrónica a una variable. Esto garantiza que tenga acceso a cualquier excepción lanzada, que se indicará en el valor de retorno.

Actualización para C # 7.0

C # 7.0 agrega una nueva característica, descartar variables: Descartes - Guía C # , que también puede ayudar a este respecto.

_ = SomeMethodAsync();

55
Eso es absolutamente asombroso. No tenía idea de que pudieras hacer esto. ¡Gracias!
Maxim Gershkovich

1
Ni siquiera es necesario decir var, solo escribe_ = SomeMethodAsync();
Ray

41

No soy particularmente aficionado a las soluciones que asignan la tarea a una variable no utilizada o que cambian la firma del método para devolver void. El primero crea código superfluo y no intuitivo, mientras que el segundo puede no ser posible si está implementando una interfaz o tiene otro uso de la función donde desea usar la Tarea devuelta.

Mi solución es crear un método de extensión de Task, llamado DoNotAwait () que no hace nada. Esto no solo suprimirá todas las advertencias, ReSharper o de otra manera, sino que hará que el código sea más comprensible e indicará a los futuros mantenedores de su código que realmente tenía la intención de que la llamada no fuera esperada.

Método de extensión:

public static class TaskExtensions
{
    public static void DoNotAwait(this Task task) { }
}

Uso:

public static void DoStuff()
{
    GetNameAsync().DoNotAwait();
    MainWorkOfApplicationIDontWantBlocked();
}

Editado para agregar: esto es similar a la solución de Jonathan Allen donde el método de extensión comenzaría la tarea si aún no se hubiera iniciado, pero prefiero tener funciones de un solo propósito para que la intención de la persona que llama sea completamente clara.


1
Me gusta, pero lo renombré a Unawait ();)
Ostati

29

async void ¡ES MALO!

  1. ¿Cuál es la diferencia entre devolver vacío y devolver una tarea?
  2. https://jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.html

Lo que sugiero es que ejecute explícitamente a Tasktravés de un método anónimo ...

p.ej

public static void DoStuff()
{
    Task.Run(async () => GetNameAsync());
    MainWorkOfApplicationIDontWantBlocked();
}

O si desea que se bloquee, puede esperar en el método anónimo

public static void DoStuff()
{
    Task.Run(async () => await GetNameAsync());
    MainWorkOfApplicationThatWillBeBlocked();
}

Sin embargo, si su GetNameAsyncmétodo tiene que interactuar con la interfaz de usuario o incluso cualquier cosa vinculada a la interfaz de usuario (WINRT / MVVM, lo estoy mirando), entonces se vuelve un poco más funky =)

Deberá pasar la referencia al despachador de la interfaz de usuario de esta manera ...

Task.Run(async () => await GetNameAsync(CoreApplication.MainView.CoreWindow.Dispatcher));

Y luego, en su método asíncrono, deberá interactuar con su UI o elementos vinculados a la UI que pensaron que el despachador ...

dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {  this.UserName = userName; });

La extensión de tu tarea es increíble. No sé por qué no he implementado uno antes.
Mikael Dúi Bolinder el

8
El primer enfoque que menciona provoca una advertencia diferente: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread. esto también hace que se cree un nuevo subproceso, mientras que un nuevo subproceso no necesariamente se creará con async / wait solo.
gregsdennis

Debería estar despierto señor!
Ozkan

16

Esto es lo que estoy haciendo actualmente:

SomeAyncFunction().RunConcurrently();

Donde RunConcurrentlyse define como ...

 /// <summary> 
 /// Runs the Task in a concurrent thread without waiting for it to complete. This will start the task if it is not already running. 
 /// </summary> 
 /// <param name="task">The task to run.</param> 
 /// <remarks>This is usually used to avoid warning messages about not waiting for the task to complete.</remarks> 
 public static void RunConcurrently(this Task task) 
 { 
     if (task == null) 
         throw new ArgumentNullException("task", "task is null."); 

     if (task.Status == TaskStatus.Created) 
         task.Start(); 
 } 

https://github.com/docevaad/Anchor/blob/master/Tortuga.Anchor/Tortuga.Anchor.source/shared/TaskUtilities.cs

https://www.nuget.org/packages/Tortuga.Anchor/


1
+1. Parece que evita todos los problemas que se comparan en los comentarios de otras respuestas. Gracias.
Grault

3
Solo necesitas esto: public static void Forget(this Task task) { }
Shital Shah

1
a qué te refieres @ShitalShah
Jay Wick

@ShitalShah que solo funcionará con tareas de inicio automático como las creadas con async Task. Algunas tareas deben iniciarse manualmente.
Jonathan Allen

7

De acuerdo con el artículo de Microsoft sobre esta advertencia, puede resolverla simplemente asignando la tarea devuelta a una variable. A continuación se muestra una traducción del código proporcionado en el ejemplo de Microsoft:

    // To suppress the warning without awaiting, you can assign the 
    // returned task to a variable. The assignment doesn't change how
    // the program runs. However, the recommended practice is always to
    // await a call to an async method.
    // Replace Call #1 with the following line.
    Task delayTask = CalledMethodAsync(delay);

Tenga en cuenta que al hacer esto aparecerá el mensaje "La variable local nunca se usa" en ReSharper.


Sí, eso suprime la advertencia, pero no, eso realmente no resuelve nada. Task-las funciones de retorno deben ser await-ed a menos que tenga una muy buena razón para no hacerlo. Aquí no hay ninguna razón por la cual descartar la tarea sería mejor que la respuesta ya aceptada de usar un async voidmétodo.

También prefiero el método vacío. Esto hace que StackOverflow sea más inteligente que Microsoft, pero por supuesto eso debería ser un hecho.
devlord

44
async voidpresenta problemas serios en torno al manejo de errores y da como resultado un código no verificable (consulte mi artículo de MSDN ). Sería mucho mejor usar la variable, si está absolutamente seguro de que quiere que se traguen las excepciones en silencio. Lo más probable es que la operación quiera comenzar dos Tasksy luego hacer un await Task.WhenAll.
Stephen Cleary

@StephenCleary Si las excepciones de tareas no observadas se ignoran es configurable. Si existe alguna posibilidad de que su código se use en el proyecto de otra persona, no permita que eso suceda. Se async void DoNotWait(Task t) { await t; }puede usar un método auxiliar simple para evitar los inconvenientes de los async voidmétodos que usted describe. (Y no creo que Task.WhenAlles lo que quiere el OP, pero podría muy bien ser.)

@hvd: su método auxiliar lo haría comprobable, pero aún tendría un soporte de manejo de excepciones muy pobre.
Stephen Cleary

3

Aquí, una solución simple.

public static class TasksExtensions
{
    public static void RunAndForget(this Task task)
    {
    }
}

Saludos


1

Es su ejemplo simplificado el que causa el código superfluo. Normalmente, desearía utilizar los datos que se obtuvieron de la fuente de bloqueo en algún momento del programa, por lo que desearía recuperar el resultado para poder acceder a los datos.

Si realmente tiene algo que sucede totalmente aislado del resto del programa, async no sería el enfoque correcto. Simplemente comience un nuevo hilo para esa tarea.


Me lo quiero utilizar los datos obtenidos de la fuente de bloqueo, pero no quiero bloquear la persona que llama mientras espero a ella. Sin asíncrono, puede lograr esto pasando una devolución de llamada. En mi ejemplo, tengo dos métodos asincrónicos que deben llamarse en secuencia, y la segunda llamada debe usar un valor devuelto por la primera. Esto significaría anidar controladores de devolución de llamada, lo que se pone feo como el infierno. La documentación que he estado leyendo sugiere que esto es específicamente lo que asyncfue diseñado para limpiar ( por ejemplo )
Barro

@Mud: simplemente envíe el resultado de la primera llamada asíncrona como parámetro a la segunda llamada. De esa manera, el código en el segundo método comienza de inmediato, y puede esperar el resultado de la primera llamada cuando lo desee.
Guffa

Puedo hacer eso, pero sin async eso significa devoluciones de llamada anidadas, como esta: MethodWithCallback((result1) => { Use(result1); MethodWithCallback((result2) => { Use(result1,result2); })incluso en este ejemplo trivial, es una perra analizar. Con asíncrono, se genera código equivalente para mí cuando escribo result1 = await AsyncMethod(); Use(result1); result2 = await AsyncMethod(); Use(result1,result2); Cuál es mucho más fácil de leer (aunque no estén muy legible aplastado juntos en este comentario!)
Mud

@ Barro: sí. Pero puede llamar al segundo método asíncrono justo después del primero, no hay razón para que espere a que se realice la llamada síncrona Use.
Guffa

Realmente no entiendo lo que estás diciendo. MethodWithCallback es asíncrono. Si los llamo de forma consecutiva sin esperar la primera llamada, la segunda llamada puede finalizar antes que la primera. Sin embargo, necesito el resultado de la primera llamada en el controlador para la segunda. Entonces tengo que esperar la primera llamada.
Barro

0

¿Realmente quieres ignorar el resultado? como incluir ignorar cualquier excepción inesperada?

Si no es así, es posible que desee echar un vistazo a esta pregunta: enfoque Fire and Forget ,


0

Si no desea cambiar la firma del método para devolver void(ya que la devolución voidsiempre debe ser anulada ), puede usar la función Descartar C # 7.0+ como esta, que es un poco mejor que asignar a una variable (y debería eliminar la mayoría de los demás advertencias de herramientas de validación de fuente):

public static void DoStuff()
{
    _ = GetNameAsync(); // we don't need the return value (suppresses warning)
    MainWorkOfApplicationIDontWantBlocked();
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.