No se puede especificar el modificador ‘async’ en el método ‘Principal’ de una aplicación de consola

Soy nuevo en la progtwigción asincrónica con el modificador async . Estoy tratando de averiguar cómo asegurarme de que mi método Main de una aplicación de consola realmente se ejecute de forma asíncrona.

 class Program { static void Main(string[] args) { Bootstrapper bs = new Bootstrapper(); var list = bs.GetList(); } } public class Bootstrapper { public async Task<List> 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 modificador async en el método Main , ¿cómo puedo ejecutar el código dentro del main forma asíncrona?

Como descubriste, en VS11 el comstackdor no permitirá un método async Main . Esto fue permitido (pero nunca recomendado) en VS2010 con el Async CTP.

Tengo publicaciones recientes en blogs sobre async / await y los progtwigs de consola asincrónicos en particular. Aquí hay información de fondo de la publicación de introducción:

Si “espera” ve que lo agitable no se ha completado, entonces actúa de forma asíncrona. Le dice a los awaitable que ejecuten el rest del método cuando se complete, y luego regresa del método async. Aguardar también capturará el contexto actual cuando pase el rest del método a la espera.

Más adelante, cuando se complete lo que se puede esperar, ejecutará el rest del método asíncrono (dentro del contexto capturado).

He aquí por qué este es un problema en los progtwigs de la consola con un async Main :

Recuerde de nuestra publicación de introducción que un método asíncrono regresará a su llamador antes de que se complete. Esto funciona perfectamente en aplicaciones de interfaz de usuario (el método simplemente regresa al bucle de eventos de IU) y las aplicaciones de ASP.NET (el método devuelve el hilo pero mantiene activa la solicitud). No funciona tan bien para los progtwigs de la consola: los principales retornan al sistema operativo, por lo que su progtwig finaliza.

Una solución es proporcionar su propio contexto: un “bucle principal” para su progtwig de consola que es compatible con la sincronización.

Si tiene una máquina con Async CTP, puede usar GeneralThreadAffineContext desde Mis Documentos \ Microsoft Visual Studio Async CTP \ Samples (Prueba C #) Unit Testing \ AsyncTestUtilities . Alternativamente, puede usar AsyncContext desde mi paquete Nito.AsyncEx NuGet .

Aquí hay un ejemplo usando AsyncContext ; GeneralThreadAffineContext tiene un uso casi idéntico:

 using Nito.AsyncEx; class Program { static void Main(string[] args) { AsyncContext.Run(() => MainAsync(args)); } static async void MainAsync(string[] args) { Bootstrapper bs = new Bootstrapper(); var list = await bs.GetList(); } } 

De forma alternativa, puede bloquear el hilo de la consola principal hasta que se complete su trabajo asincrónico:

 class Program { static void Main(string[] args) { MainAsync(args).GetAwaiter().GetResult(); } static async Task MainAsync(string[] args) { Bootstrapper bs = new Bootstrapper(); var list = await bs.GetList(); } } 

Tenga en cuenta el uso de GetAwaiter().GetResult() ; esto evita la envoltura de la AggregateException que ocurre si usa Wait() o Result .

Actualización, 30/1/2017: a partir de la Actualización 3 de Visual Studio 2017 (15.3), el idioma ahora admite una async Main , siempre que devuelva Task o Task . Entonces ahora puedes hacer esto:

 class Program { static async Task Main(string[] args) { Bootstrapper bs = new Bootstrapper(); var list = await bs.GetList(); } } 

La semántica parece ser la misma que el estilo GetAwaiter().GetResult() de bloquear el hilo principal. Sin embargo, todavía no hay especificaciones de idioma para C # 7.1, así que esto es solo una suposición.

Puedes resolver esto con esta simple construcción:

 class Program { static void Main(string[] args) { Task.Run(async () => { // Do any async anything you need here without worry }).GetAwaiter().GetResult(); } } 

Eso colocará todo lo que haga en el ThreadPool donde lo desee (para que otras Tareas que inicie / aguarde no intenten volver a un hilo que no deberían), y espere hasta que todo esté listo antes de cerrar la aplicación de consola. No hay necesidad de bucles especiales o libs externos.

Editar: Incorpore la solución de Andrew para Excepciones no detectadas.

Puede hacerlo sin necesidad de bibliotecas externas también haciendo lo siguiente:

 class Program { static void Main(string[] args) { Bootstrapper bs = new Bootstrapper(); var getListTask = bs.GetList(); // returns the Task> Task.WaitAll(getListTask); // block while the task completes var list = getListTask.Result; } } 

Agregaré una característica importante que todas las otras respuestas han pasado por alto: cancelación.

Una de las cosas más importantes de TPL es el soporte de cancelación, y las aplicaciones de consola tienen un método de cancelación integrado (CTRL + C). Es muy simple unirlos. Así es como estructurar todas mis aplicaciones de consola asíncronas:

 static void Main(string[] args) { CancellationTokenSource cts = new CancellationTokenSource(); System.Console.CancelKeyPress += (s, e) => { e.Cancel = true; cts.Cancel(); }; MainAsync(args, cts.Token).Wait(); } static async Task MainAsync(string[] args, CancellationToken token) { ... } 

En C # 7.1, podrá hacer una asincronización principal correcta. Las firmas apropiadas para el método Main se han extendido a:

 public static Task Main(); public static Task Main(); public static Task Main(string[] args); public static Task Main(string[] args); 

Por ejemplo, podrías estar haciendo:

 static async Task Main(string[] args) { Bootstrapper bs = new Bootstrapper(); var list = await bs.GetList(); } 

En tiempo de comstackción, el método de punto de entrada asíncrono se traducirá para llamar a 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í, haz clic en el botón avanzado en la parte inferior:

enter image description here

En el menú desplegable de la versión del idioma, seleccione “7.1” (o cualquier valor superior):

enter image description here

El valor predeterminado es “última versión principal” que evaluaría (en el momento de escribir este documento) a C # 7.0, que no es compatible con la función principal asíncrona en las aplicaciones de la consola.

Todavía no necesitaba mucho, pero cuando utilicé la aplicación de consola para las pruebas rápidas y la asincronía requerida, lo resolví así:

 class Program { static void Main(string[] args) { MainAsync(args).Wait(); } static async Task MainAsync(string[] args) { // Code here } } 

C # 7.1 (utilizando vs 2017 actualización 3) introduce asincronización principal

Puedes escribir:

  static async Task Main(string[] args) { await ... } 

Para más detalles C # 7 Series, Parte 2: Async Principal

Actualizar:

Puede obtener un error de comstackción:

El progtwig no contiene un método estático “Principal” adecuado para un punto de entrada

Este error se debe a que vs2017.3 está configurado de manera predeterminada como c # 7.0 no c # 7.1.

Debe modificar explícitamente la configuración de su proyecto para establecer las características de c # 7.1.

Puede establecer c # 7.1 por dos métodos:

Método 1: utilizando la ventana de configuración del proyecto:

  • Abra la configuración de su proyecto
  • Selecciona la pestaña Construir
  • Haga clic en el botón Avanzado
  • Seleccione la versión que desee Como se muestra en la siguiente figura:

enter image description here

Método2: modificar PropertyGroup of .csproj manualmente

Agregue esta propiedad:

  7.1 

ejemplo:

   AnyCPU true full false bin\Debug\ DEBUG;TRACE prompt 4 false 7.1  

Si está utilizando C # 7.1 o posterior, vaya con la respuesta de nawfal y simplemente cambie el tipo de devolución de su método Principal a Task o Task . Si no eres:

  • Tener una async Task MainAsync como dijo Johan .
  • Llame a .GetAwaiter().GetResult() para detectar la excepción subyacente como do0g dijo .
  • Cancelación de soporte como dijo Cory .
  • Un segundo CTRL+C debe finalizar el proceso de inmediato. (Gracias binki !)
  • Maneje OperationCancelledException – devuelva un código de error apropiado.

El código final se ve así:

 private static int Main(string[] args) { var cts = new CancellationTokenSource(); Console.CancelKeyPress += (s, e) => { e.Cancel = !cts.IsCancellationRequested; cts.Cancel(); }; try { return MainAsync(args, cts.Token).GetAwaiter().GetResult(); } catch (OperationCanceledException) { return 1223; // Cancelled. } } private static async Task MainAsync(string[] args, CancellationToken cancellationToken) { // Your code... return await Task.FromResult(0); // Success. } 

Para llamar asíncronamente a la tarea desde Main, use

  1. Task.Run () para .NET 4.5

  2. Task.Factory.StartNew () para .NET 4.0 (puede requerir la biblioteca Microsoft.Bcl.Async para asincrónica y esperar palabras clave)

Detalles: http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx

En Main intente cambiar la llamada a GetList para:

 Task.Run(() => bs.GetList()); 

Cuando se introdujo el CTP C # 5, ciertamente se podía marcar Main con async … aunque en general 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 subprocesos en primer plano , su progtwig se cerrará cuando se complete Main , incluso si ha comenzado algún trabajo en segundo plano.

¿Qué estás realmente tratando de hacer? Tenga en cuenta que su método GetList() realmente no necesita ser asincrónico en este momento; está agregando una capa adicional sin motivo real. Es lógicamente equivalente a (pero más complicado que):

 public Task> GetList() { return new GetPrograms().DownloadTvChannels(); } 

En MSDN, la documentación para Task.Run Method (Action) proporciona este ejemplo que muestra cómo ejecutar un método de forma asincrónica desde main :

 using System; using System.Threading; using System.Threading.Tasks; public class Example { public static void Main() { ShowThreadInfo("Application"); var t = Task.Run(() => ShowThreadInfo("Task") ); t.Wait(); } static void ShowThreadInfo(String s) { Console.WriteLine("{0} Thread ID: {1}", s, Thread.CurrentThread.ManagedThreadId); } } // The example displays the following output: // Application thread ID: 1 // Task thread ID: 3 

Tenga en cuenta esta statement que sigue el ejemplo:

Los ejemplos muestran que la tarea asíncrona se ejecuta en un subproceso diferente del subproceso principal de la aplicación.

Por lo tanto, 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 el comentario de Stephen sobre su respuesta:

Puedes usar un simple Wait o Result , y no hay nada de malo en eso. Pero tenga en cuenta que existen dos diferencias importantes: 1) todas las continuidades async se ejecutan en el grupo de subprocesos en lugar del subproceso principal, y 2) las excepciones se envuelven en una Excepción de AggregateException .

(Consulte Manejo de excepciones (Biblioteca paralela de tareas) para obtener información sobre cómo incorporar el manejo de excepciones para tratar una excepción AggregateException ).


Finalmente, en MSDN desde la documentación para Task.Delay Method (TimeSpan) , este ejemplo muestra cómo ejecutar una tarea asíncrona que devuelve un valor:

 using System; using System.Threading.Tasks; public class Example { public static void Main() { var t = Task.Run(async delegate { await Task.Delay(TimeSpan.FromSeconds(1.5)); return 42; }); t.Wait(); Console.WriteLine("Task t Status: {0}, Result: {1}", t.Status, t.Result); } } // The example displays the following output: // Task t Status: RanToCompletion, Result: 42 

Tenga en cuenta que en lugar de pasar un delegate a Task.Run , en su lugar puede pasar una función lambda como esta:

 var t = Task.Run(async () => { await Task.Delay(TimeSpan.FromSeconds(1.5)); return 42; }); 

La versión más reciente 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 a al menos 15.3 y cambiar la versión de C# 7.1 a C# 7.1 o la C# latest minor version . Para hacer esto, vaya a Propiedades del proyecto -> Construir -> Avanzado -> Versión del idioma.

Después de esto, el siguiente código funcionará:

 internal class Program { public static async Task Main(string[] args) { (...) } 

Para evitar la congelación cuando se llama a una función en algún lugar de la stack de llamadas que intenta volver a unirse al hilo actual (que está atascado en Esperar), debe hacer lo siguiente:

 class Program { static void Main(string[] args) { Bootstrapper bs = new Bootstrapper(); List list = Task.Run((Func>>)bs.GetList).Result; } } 

(el elenco solo es necesario para resolver la ambigüedad)

En mi caso, tenía una lista de trabajos que quería ejecutar de manera asincrónica desde mi método principal, he estado usando esto en producción durante bastante tiempo y funciona bien.

 static void Main(string[] args) { Task.Run(async () => { await Task.WhenAll(jobslist.Select(nl => RunMulti(nl))); }).GetAwaiter().GetResult(); } private static async Task RunMulti(List joblist) { await ... }