A continuación se muestra mi código simple que ejecuta un IHostedService
internal class Program { public static Task Main(string[] args) { var host = new HostBuilder().ConfigureServices((hostcontext, services) => { services.AddHostedService<MyService>(); }).Build(); host.Run(); return Task.CompletedTask; } } public class MyService : IHostedService { public Task StartAsync(CancellationToken cancellationToken) { return Task.Run(() => { while (true) { Console.WriteLine("Starting Service"); } }); } public Task StopAsync(CancellationToken cancellationToken) { Console.WriteLine("Stopping service"); return Task.CompletedTask; } }Entonces, cuando quiero detener el servicio presionando ctrl + C en la consola, espero ver que el servicio se detiene y la consola imprime "Deteniendo el servicio".
Pero cuando presiono Ctrl + C, el servicio continúa ejecutándose en el ciclo infinito, lo cual no entiendo.
Creo que Task.Run() en cola un elemento de trabajo en el grupo de subprocesos, luego un subproceso de fondo del grupo de subprocesos para retomar el trabajo, por lo que en mi ejemplo, es un subproceso de trabajo (la identificación del subproceso de trabajo es 4, la identificación del subproceso principal es 1) que ejecuta el ciclo while. Entonces, cuando presiono ctrl + S, el servicio debe detenerse, entonces, ¿por qué el subproceso en segundo plano en ejecución detiene el servicio? ¿No es eso cuando una aplicación termina, todos los trabajos/subprocesos en segundo plano también finalizan? Quiero decir, si Task.Run() se ejecuta crea subprocesos en primer plano, entonces puedo entender, porque todos los subprocesos en primer plano deben finalizar antes de que se pueda detener la aplicación.
PD:
Puedo pasar CancellationToken para detener el ciclo while, entiendo que puedo hacer eso y en el ciclo while, compruebo si se puede llamar al token, etc.
pero no entiendo por qué tengo que hacer eso, porque el subproceso en ejecución es un subproceso de fondo, no un subproceso de primer plano, entonces, ¿por qué todos los subprocesos de fondo deben terminar primero y se puede invocar StopAsync() ? es decir , ¿cómo un subproceso en segundo plano en ejecución detiene el flujo de ejecución que llega a StopAsync() ?
Si desea detener una tarea, intente usar un token de cancelación en MyServiceClass
Aquí está mi ejemplo:
public class MyService : IHostedService { private CancellationTokenSource _ts; public Task StartAsync(CancellationToken cancellationToken) { _ts = new CancellationTokenSource(); return Task.Factory.StartNew(() => { while (true) { Console.WriteLine("Starting Service"); Thread.Sleep(500); if (_ts.Token.IsCancellationRequested) { Console.WriteLine("Stopping service"); break; } } }, _ts.Token); } public Task StopAsync(CancellationToken cancellationToken) { _ts.Cancel(); return Task.CompletedTask; } }La razón por la que Ctrl+C no finaliza directamente la aplicación de la consola, como podría estar acostumbrado desde las aplicaciones de consola "ordinarias", es que en realidad hay soporte en .NET para evitar que Ctrl+C finalice la aplicación, y para la aplicación para reaccionar a Ctrl+C y "hacer cosas".
El marco de alojamiento que está utilizando ha utilizado este sistema, un evento, para evitar que su aplicación se termine por la fuerza, así como para asumir el procedimiento de apagado.
El evento en sí es Console.CancelKeyPress y este es un evento cancelable, lo que significa que puede regresar desde el controlador de eventos y establecer una marca en el objeto EventArgs para indicarle al código que invocó su controlador de eventos que desea cancelar el manejo predeterminado de Ctrl+C.
El marco de alojamiento ha hecho esto.
Puede ver el código exacto aquí: Microsoft.Extensions.Hosting/Internal/ConsoleLifetime.cs @48-52 :
Console.CancelKeyPress += (sender, e) => { e.Cancel = true; ApplicationLifetime.StopApplication(); };Además de cancelar el procedimiento de apagado normal, el controlador de eventos proporcionado por el marco de alojamiento también cancela un CancellationToken que es el token que se pasa a StartAsync. Como usted dice, si pasa este token a sus tareas y reacciona ante su cancelación, su aplicación se cierra.
La llamada a ApplicationLifetime.StopApplication eventualmente termina en Microsoft.Extensions.Hosting/Internal/ApplicationLifetime.cs @102-112 :
private void ExecuteHandlers(CancellationTokenSource cancel) { // Noop if this is already cancelled if (cancel.IsCancellationRequested) { return; } // Run the cancellation token callbacks cancel.Cancel(throwOnFirstException: false); }Así que ahí lo tienes. La razón por la que su aplicación sigue girando en un ciclo infinito es precisamente porque el marco de alojamiento evita el apagado normal y, en cambio, le da a sus tareas la oportunidad de completarse de manera ordenada. Dado que sus tareas ignoran esta solicitud y siguen ejecutándose, su proceso nunca termina cuando presiona Ctrl+C.