c# - net - No se puede especificar el modificador ''asíncrono'' en el método ''Principal'' de una aplicación de consola
task c# (15)
Agregaré una característica importante que todas las demás respuestas han pasado por alto: la cancelación.
Una de las cosas importantes en 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 estructuro 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)
{
...
}
Soy nuevo en la programación asíncrona con el modificador async
. Estoy tratando de averiguar cómo asegurarme de que mi método Main
de una aplicación de consola 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<TvChannel>> GetList()
{
GetPrograms pro = new GetPrograms();
return await pro.DownloadTvChannels();
}
}
Sé que esto no se ejecuta de forma asincrónica desde "la parte superior". Dado que no es posible especificar el modificador async
en el método Main
, ¿cómo puedo ejecutar el código dentro de la asincronía main
?
C # 7.1 (usando vs 2017 actualización 3) introduce la página principal asíncrona
Puedes escribir:
static async Task Main(string[] args)
{
await ...
}
Para más detalles Serie C # 7, Parte 2: Principal asíncrono
Actualizar:
Puede obtener un error de compilación:
El programa no contiene un método estático ''Principal'' adecuado para un punto de entrada
Este error se debe a que vs2017.3 se configura 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 configurar c # 7.1 por dos métodos:
Método 1: Usando la ventana de configuración del proyecto:
- Abre la configuración de tu proyecto.
- Seleccione la pestaña Construir
- Haga clic en el botón Avanzado
- Seleccione la versión que desee Como se muestra en la siguiente figura:
Método 2: Modificar PropertyGroup de .csproj manualmente
Añadir esta propiedad:
<LangVersion>7.1</LangVersion>
ejemplo:
<PropertyGroup Condition=" ''$(Configuration)|$(Platform)'' == ''Debug|AnyCPU'' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin/Debug/</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
<LangVersion>7.1</LangVersion>
</PropertyGroup>
Como descubrió, en VS11 el compilador no permitirá un método async Main
. Esto se permitió (pero nunca se recomendó) en VS2010 con el CTP de Async.
Tengo publicaciones de blog recientes sobre async/await y programas de consola asíncronos en particular. Aquí hay algo de información de fondo de la publicación de introducción:
Si "a la espera" ve que lo esperado no se ha completado, entonces actúa de forma asíncrona. Le dice a la persona que espera que ejecute el resto del método cuando se complete, y luego regresa del método asíncrono. Await también capturará el contexto actual cuando pase el resto del método a la espera.
Más adelante, cuando finalice lo esperado, ejecutará el resto del método asíncrono (dentro del contexto capturado).
He aquí por qué esto es un problema en los programas de la consola con un async Main
:
Recuerde de nuestra publicación de introducción que un método asíncrono volverá a su interlocutor antes de que se complete. Esto funciona perfectamente en las aplicaciones de la interfaz de usuario (el método solo regresa al bucle de eventos de la interfaz de usuario) y las aplicaciones de ASP.NET (el método se retira del hilo pero mantiene la solicitud activa). No funciona tan bien para los programas de la Consola: Main vuelve al sistema operativo, por lo que su programa sale.
Una solución es proporcionar su propio contexto: un "bucle principal" para su programa de consola que es compatible con async.
Si tiene una máquina con el CTP asíncrono, puede usar GeneralThreadAffineContext
desde Mis documentos / Microsoft Visual Studio CTP asíncrono / Pruebas unitarias (Pruebas de C #) / Pruebas asíncronas . Alternativamente, puede usar AsyncContext
de 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();
}
}
Alternativamente, puede bloquear el hilo de la Consola principal hasta que su trabajo asíncrono haya finalizado:
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 el AggregateException
que ocurre si usa Wait()
o Result
.
Actualización, 2017-11-30: A partir de la actualización 3 de Visual Studio 2017 (15.3), el lenguaje ahora admite una async Main
, siempre que devuelva Task
o Task<T>
. Así que 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 la del estilo GetAwaiter().GetResult()
de bloquear el hilo principal. Sin embargo, aún no existe una especificación de idioma para C # 7.1, así que esto es solo una suposición.
Cuando se introdujo el CTP C # 5, ciertamente podría marcar Main con async
... aunque generalmente no fue 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 de primer plano , su programa se cerrará cuando finalice Main
, incluso si se ha iniciado algún trabajo de fondo.
¿Qué estás realmente tratando de hacer? Tenga en cuenta que su método GetList()
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):
public Task<List<TvChannel>> GetList()
{
return new GetPrograms().DownloadTvChannels();
}
En C # 7.1 podrás hacer un Main async apropiado. Las firmas apropiadas para el método Main
se han extendido a:
public static Task Main();
public static Task<int> Main();
public static Task Main(string[] args);
public static Task<int> 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 el momento de la compilació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 funciones de 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 "la versión principal más reciente", que evaluaría (en el momento de esta publicación) a C # 7.0, que no admite la función principal asíncrona en aplicaciones de consola.
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
:
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
Note esta declaración que sigue al ejemplo:
Los ejemplos muestran que la tarea asíncrona se ejecuta en un hilo diferente al del hilo 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 en .
Y con respecto al hilo en el que se ejecuta la tarea, también tenga en cuenta el comment de Stephen sobre su respuesta:
Puedes usar una simple
Wait
oResult
, y no hay nada de malo en eso. Pero tenga en cuenta que hay dos diferencias importantes: 1) todas las continuacionesasync
se ejecutan en el grupo de subprocesos en lugar de en el subproceso principal, y 2) todas las excepciones están envueltas en una excepciónAggregateException
.
(Consulte Manejo de excepciones (Task Parallel Library) para saber cómo incorporar el manejo de excepciones para tratar con una excepción AggregateException
).
Finalmente, en MSDN de la documentación del Método Task.Delay (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
, puede pasar una función lambda como esta:
var t = Task.Run(async () =>
{
await Task.Delay(TimeSpan.FromSeconds(1.5));
return 42;
});
En Main intenta cambiar la llamada a GetList para:
Task.Run(() => bs.GetList());
En mi caso, tenía una lista de trabajos que quería ejecutar de forma asíncrona a 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<string> joblist)
{
await ...
}
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 -> Compilar -> Avanzado -> Versión de 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 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:
class Program
{
static void Main(string[] args)
{
Bootstrapper bs = new Bootstrapper();
List<TvChannel> list = Task.Run((Func<Task<List<TvChannel>>>)bs.GetList).Result;
}
}
(El elenco solo es requerido para resolver la ambigüedad)
Para la tarea de llamada asíncrona 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
Puede hacer esto 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<List<TvChannel>>
Task.WaitAll(getListTask); // block while the task completes
var list = getListTask.Result;
}
}
Puedes resolver esto con este simple constructo:
class Program
{
static void Main(string[] args)
{
Task.Run(async () =>
{
// Do any async anything you need here without worry
}).GetAwaiter().GetResult();
}
}
Eso pondrá todo lo que haga en ThreadPool donde lo desee (para que las otras Tareas que comience / espere no intente volver a unirse a un Thread que no deberían), y espere hasta que todo haya terminado antes de cerrar la aplicación de la Consola. No hay necesidad de bucles especiales o librerías externas.
Edición: Incorporar la solución de Andrew para Excepciones no detectadas.
Si está utilizando C # 7.1 o posterior, vaya con la respuesta del nawfal y simplemente cambie el tipo de retorno de su método principal a Task
o Task<int>
. Si no eres:
- Tenga una
async Task MainAsync
como dijo Johan . - Llame a su
.GetAwaiter().GetResult()
para detectar la excepción subyacente, como dijo do0g . - Apoyo a la cancelación como dijo Cory .
- Un segundo
CTRL+C
debe terminar el proceso inmediatamente. (Gracias binki !) - Manejar
OperationCancelledException
: devolver 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<int> MainAsync(string[] args, CancellationToken cancellationToken)
{
// Your code...
return await Task.FromResult(0); // Success.
}
Todavía no he necesitado tanto, pero cuando he usado la aplicación de consola para pruebas rápidas y he requerido async, lo he resuelto así:
class Program
{
static void Main(string[] args)
{
MainAsync(args).Wait();
}
static async Task MainAsync(string[] args)
{
// Code here
}
}