Soy nuevo en la programación asincrónica con el async
modificador. Estoy tratando de descubrir cómo asegurarme de que mi Main
método de una aplicación de consola realmente se ejecute de forma asincrónica.
class Program
{
static void Main(string[] args)
{
Bootstrapper bs = new Bootstrapper();
var list = bs.GetList();
}
}
public class Bootstrapper {
public async Task<List<TvChannel>> GetList()
{
GetPrograms pro = new GetPrograms();
return await pro.DownloadTvChannels();
}
}
Sé que esto no se ejecuta de forma asincrónica desde "la parte superior". Como no es posible especificar el async
modificador en el Main
método, ¿cómo puedo ejecutar el código de main
forma asincrónica?
c#
.net
asynchronous
console-application
danielovich
fuente
fuente
Respuestas:
Como descubrió, en VS11 el compilador no permitirá un
async Main
método. Esto fue permitido (pero nunca recomendado) en VS2010 con el Async CTP.Tengo publicaciones de blog recientes sobre programas de consola asíncronos / en espera y asíncronos en particular. Aquí hay información de fondo de la publicación de introducción:
He aquí por qué este es un problema en los programas de consola con un
async Main
:Una solución es proporcionar su propio contexto: un "bucle principal" para su programa de consola que sea compatible con la sincronización.
Si tiene una máquina con el CTP asíncrono, puede usar
GeneralThreadAffineContext
desde Mis documentos \ Microsoft Visual Studio CTP asíncrono \ Muestras (Prueba de C #) Prueba de unidad \ AsyncTestUtilities . Alternativamente, puede usarAsyncContext
desde mi paquete Nito.AsyncEx NuGet .Aquí hay un ejemplo usando
AsyncContext
;GeneralThreadAffineContext
tiene un uso casi idéntico:Alternativamente, puede bloquear el hilo principal de la consola hasta que su trabajo asincrónico se haya completado:
Tenga en cuenta el uso de
GetAwaiter().GetResult()
; esto evita laAggregateException
envoltura que ocurre si usaWait()
oResult
.Actualización, 2017-11-30: a partir de Visual Studio 2017 Actualización 3 (15.3), el idioma ahora admite un
async Main
- siempre que regreseTask
oTask<T>
. Entonces ahora puedes hacer esto:La semántica parece ser la misma que el
GetAwaiter().GetResult()
estilo de bloquear el hilo principal. Sin embargo, todavía no hay especificaciones de lenguaje para C # 7.1, por lo que esto es solo una suposición.fuente
Wait
oResult
, y no hay nada de malo en eso. Pero tenga en cuenta que hay dos diferencias importantes: 1) todas lasasync
continuaciones se ejecutan en el grupo de subprocesos en lugar del subproceso principal, y 2) las excepciones se envuelven en unAggregateException
.<LangVersion>latest</LangVersion>
al archivo csproj, como se muestra aquí .Puede resolver esto con esta construcción simple:
Eso colocará todo lo que haga en el ThreadPool donde lo desee (por lo que otras Tareas que inicie / espere no intenten unirse a un Thread que no deberían), y espere hasta que todo esté listo antes de cerrar la aplicación Consola. No necesita bucles especiales ni libs externos.
Editar: incorpore la solución de Andrew para excepciones no detectadas.
fuente
Wait()
conGetAwaiter().GetResult()
evitará laAggregateException
envoltura cuando las cosas se tiran.async main
se está introduciendo en C # 7.1, a partir de este escrito.Puede hacer esto sin necesidad de bibliotecas externas también haciendo lo siguiente:
fuente
getListTask.Result
también es una llamada de bloqueo, por lo que el código anterior podría escribirse sin élTask.WaitAll(getListTask)
.GetList
lanza, tendrá que atraparAggregateException
e interrogar sus excepciones para determinar la excepción real lanzada. Sin embargo, puede llamarGetAwaiter()
para obtener elTaskAwaiter
paraTask
, y llamarGetResult()
a eso, es decirvar list = getListTask.GetAwaiter().GetResult();
. Al obtener el resultado deTaskAwaiter
(también una llamada de bloqueo), las excepciones lanzadas no se incluirán en unAggregateException
.En C # 7.1 podrá hacer una sincronización asíncrona adecuada . Las firmas apropiadas para el
Main
método se han extendido a:Por ejemplo, podrías estar haciendo:
En tiempo de compilación, el método del punto de entrada asíncrono se traducirá a la llamada
GetAwaitor().GetResult()
.Detalles: https://blogs.msdn.microsoft.com/mazhou/2017/05/30/c-7-series-part-2-async-main
EDITAR:
Para habilitar las características del lenguaje C # 7.1, debe hacer clic derecho en el proyecto y hacer clic en "Propiedades" y luego ir a la pestaña "Crear". Allí, haga clic en el botón avanzado en la parte inferior:
En el menú desplegable de la versión de idioma, seleccione "7.1" (o cualquier valor superior):
El valor predeterminado es "última versión principal" que evaluaría (en el momento de escribir este artículo) a C # 7.0, que no es compatible con async main en las aplicaciones de consola.
fuente
Agregaré una característica importante que todas las otras respuestas han pasado por alto: la cancelación.
Una de las grandes cosas de TPL es el soporte de cancelación, y las aplicaciones de consola tienen un método de cancelación incorporado (CTRL + C). Es muy simple unirlos. Así es como estructuro todas mis aplicaciones de consola asíncronas:
fuente
Wait()
?Wait()
, no esperará a que termine el código asincrónico; dejará de esperar y finalizará el proceso de inmediato.Wait()
método pasa el mismo token. Lo que intento decir es que no parece hacer ninguna diferencia.C # 7.1 (usando vs 2017 actualización 3) presenta async main
Puedes escribir:
Para más detalles C # 7 Series, Part 2: Async Main
Actualizar:
Puede obtener un error de compilación:
Este error se debe a que vs2017.3 está configurado de forma predeterminada como c # 7.0 no c # 7.1.
Debe modificar explícitamente la configuración de su proyecto para configurar las características de c # 7.1.
Puede configurar c # 7.1 por dos métodos:
Método 1: Usando la ventana de configuración del proyecto:
Método 2: Modificar manualmente PropertyGroup de .csproj
Añadir esta propiedad:
ejemplo:
fuente
Si está utilizando C # 7.1 o posterior, vaya con la respuesta de nawfal y simplemente cambie el tipo de retorno de su método Main a
Task
oTask<int>
. Si no eres:async Task MainAsync
como dijo Johan ..GetAwaiter().GetResult()
para atrapar la excepción subyacente como dijo do0g .CTRL+C
debe terminar el proceso de inmediato. (¡Gracias, binki !)OperationCancelledException
: devuelve un código de error apropiado.El código final se ve así:
fuente
e.Cancel = true
es incondicional.Todavía no he necesitado tanto, pero cuando utilicé la aplicación de consola para las pruebas rápidas y requirí asíncrono, lo resolví así:
fuente
SynchronizationContext
asociado con el hilo principal. Por lo tanto, no se bloqueará porque incluso sin élConfigureAwait(false)
, todas las continuaciones se ejecutarán en el conjunto de hilos.Para llamar a la tarea asincrónicamente desde Main, use
Task.Run () para .NET 4.5
Task.Factory.StartNew () para .NET 4.0 (puede requerir la biblioteca Microsoft.Bcl.Async para async y esperar palabras clave)
Detalles: http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx
fuente
En Principal, intente cambiar la llamada a GetList por:
fuente
Cuando se introdujo el C # 5 CTP, ciertamente podría marcar Main con
async
... aunque generalmente no era una buena idea hacerlo. Creo que esto fue cambiado por el lanzamiento de VS 2013 para convertirse en un error.A menos que haya iniciado otros hilos de primer plano , su programa se cerrará cuando
Main
complete, incluso si ha comenzado un trabajo de fondo.¿Qué es lo que realmente intentas hacer? Tenga en cuenta que su
GetList()
método realmente no necesita ser asíncrono en este momento: está agregando una capa adicional sin ninguna razón real. Es lógicamente equivalente a (pero más complicado que):fuente
DownloadTvChannels()
devuelve? Presumiblemente devuelve unTask<List<TvChannel>>
¿no? Si no, es poco probable que puedas esperarlo. (Posible, dado el patrón de camarero, pero improbable). En cuanto alMain
método, aún debe ser estático ... ¿ quizás reemplazó elstatic
modificador con elasync
modificador?public static async void Main() {}
? Pero siDownloadTvChannels()
ya devuelve unTask<List<TvChannel>>
, presumiblemente ya es asíncrono, por lo que no necesita agregar otra capa. Vale la pena entender esto con cuidado.La versión más nueva de C # - C # 7.1 permite crear una aplicación de consola asíncrona. Para habilitar C # 7.1 en el proyecto, debe actualizar su VS al menos a 15.3 y cambiar la versión de C # a
C# 7.1
oC# latest minor version
. Para hacer esto, vaya a Propiedades del proyecto -> Compilación -> Avanzado -> Versión del idioma.Después de esto, el siguiente código funcionará:
fuente
En MSDN, la documentación del Método Task.Run (Acción) proporciona este ejemplo que muestra cómo ejecutar un método de forma asíncrona desde
main
:Tenga en cuenta esta declaración que sigue el ejemplo:
Entonces, si en cambio desea que la tarea se ejecute en el hilo principal de la aplicación, vea la respuesta de @StephenCleary .
Y con respecto al hilo en el que se ejecuta la tarea, también tenga en cuenta Stephen's comentario sobre su respuesta:
(Consulte Manejo de excepciones (Biblioteca paralela de tareas) para obtener información sobre cómo incorporar el manejo de excepciones para tratar con un
AggregateException
.)Finalmente, en MSDN de la documentación para Task.Delay Method (TimeSpan) , este ejemplo muestra cómo ejecutar una tarea asincrónica que devuelve un valor:
Tenga en cuenta que en lugar de pasar un
delegate
aTask.Run
, puede pasar una función lambda como esta:fuente
Para evitar la congelación cuando llama a una función en algún lugar de la pila de llamadas que intenta volver a unirse al hilo actual (que está atascado en una Espera), debe hacer lo siguiente:
(el reparto solo es necesario para resolver la ambigüedad)
fuente
En mi caso, tenía una lista de trabajos que quería ejecutar de forma asíncrona desde mi método principal, he estado usando esto en producción durante bastante tiempo y funciona bien.
fuente