Estoy creando una aplicación de consola .NET Core simple que leerá las opciones básicas desde la línea de comandos, luego se ejecutará y finalizará sin interacción del usuario. Me gustaría aprovechar DI, lo que me lleva a usar el host genérico .NET Core.
Todos los ejemplos que he encontrado que crean una aplicación de consola crean una clase que implementa IHostedService o amplía BackgroundService. Luego, esa clase se agrega al contenedor de servicios a través de AddHostedService e inicia el trabajo de la aplicación a través de StartAsync o ExecuteAsync. Sin embargo, parece que en todos estos ejemplos, están implementando un servicio en segundo plano o alguna otra aplicación que se ejecuta en un bucle o espera solicitudes hasta que el sistema operativo lo apaga o recibe alguna solicitud para terminar. ¿Qué sucede si solo quiero una aplicación que se inicia, hace lo suyo y luego se cierra? Por ejemplo:
Programa.cs:
namespace MyApp { using System; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; public static class Program { public static async Task Main(string[] args) { await CreateHostBuilder(args).RunConsoleAsync(); } private static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .UseConsoleLifetime() .ConfigureLogging(builder => builder.SetMinimumLevel(LogLevel.Warning)) .ConfigureServices((hostContext, services) => { services.Configure<MyServiceOptions>(hostContext.Configuration); services.AddHostedService<MyService>(); services.AddSingleton(Console.Out); }); } }MisOpcionesDeServicio.cs:
namespace MyApp { public class MyServiceOptions { public int OpCode { get; set; } public int Operand { get; set; } } }MiServicio.cs:
namespace MyApp { using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; public class MyService : IHostedService { private readonly MyServiceOptions _options; private readonly TextWriter _outputWriter; public MyService(TextWriter outputWriter, IOptions<MyServiceOptions> options) { _options = options.Value; _outputWriter = outputWriter; } public async Task StartAsync(CancellationToken cancellationToken) { _outputWriter.WriteLine("Starting work"); DoOperation(_options.OpCode, _options.Operand); _outputWriter.WriteLine("Work complete"); } public async Task StopAsync(CancellationToken cancellationToken) { _outputWriter.WriteLine("StopAsync"); } protected void DoOperation(int opCode, int operand) { _outputWriter.WriteLine("Doing {0} to {1}...", opCode, operand); // Do work that might take awhile } } }Este código se compila y se ejecuta correctamente, produciendo el siguiente resultado:
Starting work Doing 1 to 2... Work completeSin embargo, después de eso, la aplicación simplemente se quedará ahí esperando hasta que presione Ctrl+C. Sé que podría forzar el cierre de la aplicación después de completar el trabajo, pero en este punto, siento que no estoy usando IHostedService correctamente. Parece que está diseñado para procesos recurrentes en segundo plano y no para aplicaciones de consola simples como esta. Sin embargo, en una aplicación real en la que DoOperation puede demorar entre 20 y 30 minutos, me gustaría aprovechar el método StopAsync para realizar la limpieza antes de finalizar. También sé que podría crear el contenedor de servicios yo mismo y todo eso, pero el host genérico .NET Core ya hace muchas cosas que me gustaría hacer de todos modos. Parece ser la forma correcta de escribir aplicaciones de consola, pero sin agregar un servicio alojado que inicie el trabajo real, ¿cómo puedo hacer que la aplicación realmente haga algo?
En lugar de un servicio alojado, recomendaría lo siguiente;
using (var host = CreateHostBuilder(args).Build()) { await host.StartAsync(); var lifetime = host.Services.GetRequiredService<IHostApplicationLifetime>(); // do work here / get your work service ... lifetime.StopApplication(); await host.WaitForShutdownAsync(); }Sé que podría forzar el cierre de la aplicación después de completar el trabajo, pero en este punto, siento que no estoy usando IHostedService correctamente.
Estoy de acuerdo en que parece extraño. De hecho, siempre detengo la aplicación al final de todas mis implementaciones de IHostedService . Esto es cierto incluso para aplicaciones de servidor de ejecución prolongada. Si un servicio alojado se detiene (o falla), quiero explícitamente que finalice la aplicación.
Parece que este diseño no estaba terminado cuando se eliminó .NET Core. Hay partes del diseño que están hechas para que los servicios alojados y sus aplicaciones puedan tener vidas independientes, pero dado que no se pueden reiniciar, esto simplemente no es útil en la práctica. Entonces termina pareciendo un mal diseño porque las vidas no pueden ser independientes, pero son independientes por defecto.
Todos mis servicios alojados terminan vinculando su vida útil a la vida útil de la aplicación .
finally { _hostApplicationLifetime.StopApplication(); }