1

I'm wondering how can I shutdown the IHost's execution after e.g. 30 seconds:

IHost host = new HostBuilder()
                .UseConsoleLifetime()
                .Build();

using (host)
{
   await host.RunAsync();
}

I tried to use

services.AddOptions<HostOptions>().Configure(
        opts => opts.ShutdownTimeout = TimeSpan.FromSeconds(10));

but without luck

V. S.
  • 1,086
  • 14
  • 14
Tony
  • 12,405
  • 36
  • 126
  • 226
  • Does this answer your question? [How to apply HostOptions.ShutdownTimeout when configuring .NET Core Generic Host?](https://stackoverflow.com/questions/52915015/how-to-apply-hostoptions-shutdowntimeout-when-configuring-net-core-generic-host) – Preben Huybrechts Jul 16 '20 at 10:38

2 Answers2

1

Please take a look at the code of my .NET Core 3.1 console application and the attached screenshot with console output. This application demonstrates the requested functionality. When the host is started the hosted service prints numbers to console, one number per 2 seconds. For shutting down the host you can press Enter or Ctrl + C. As soon as a command to shut down is received by the host the message "Application is shutting down..." will be written to console while the hosted service will continue its work. When the specified timeout expires the application is closed. If you want to allow to handle canceling an executed task in the hosted service, my commented code in the method ExecuteAsync demonstrates it, then the host completes its work just after canceling the executed task without waiting for the specified timeout.

        using System;
        using System.Threading;
        using System.Threading.Tasks;
        using Microsoft.Extensions.Hosting;
        using Microsoft.Extensions.DependencyInjection;

        namespace ConsoleApp38
        {
            public class Program
            {
                public static async Task Main(string[] args)
                {
                    try
                    {
                        using (var host = new MyHost())
                        {
                            await host.StartAsync();
                            Console.WriteLine(@"Host has started. Press Enter to stop the host with 30 seconds timeout");

                            while (true)
                               if (Console.KeyAvailable)
                                  if (Console.ReadKey(true).Key == ConsoleKey.Enter)
                                      await host.StopAsync();
                        }
                    }
                    catch (Exception e)
                    {
                        //log any exceptions here;
                    }
                }
            }

            public class MyHost : IHost
            {
                private static IHost _host;
                public MyHost()
                {
                    IHostBuilder builder = Host.CreateDefaultBuilder()
                        .ConfigureServices((hostContext, services) =>
                        {
                            services.Configure<HostOptions>(option =>
                            {
                                option.ShutdownTimeout = System.TimeSpan.FromSeconds(30);
                            });
                            services.AddHostedService<MyHostedService>();
                        });
                    _host = builder.Build();
                }

                public IServiceProvider Services
                {
                    get { return _host.Services; }
                }

                public void Dispose()
                {
                    _host.Dispose();
                }

                public async Task StartAsync(CancellationToken cancellationToken = default(CancellationToken))
                {
                    await _host.StartAsync(cancellationToken);
                }

                public async Task StopAsync(CancellationToken cancellationToken = default(CancellationToken))
                {
                    await _host.StopAsync(cancellationToken);
                }
            }

            public class MyHostedService : BackgroundService
            {
                public async Task StartAsync(CancellationToken cancellationToken)
                {

                }

                public async Task StopAsync(CancellationToken cancellationToken)
                {

                }

                protected override async Task ExecuteAsync(CancellationToken stoppingToken)
                {
                    for (int i = 0; i <= 100; i++)
                    {
                        //if you allow canceling this task here then the host will be shut down
                        //right after completion of this method without the specified timeout

                        //if (stoppingToken.IsCancellationRequested) break;
                        await Task.Delay(2000);
                        Console.WriteLine(i);
                    }
                }
            }
        }

The output: enter image description here

While using this application with calling host.StopAsync() after Console.ReadLine() you can use Ctrl+C or Enter to start the specified time-out before shutting down on Windows 7 64-bit, on other systems you need to press Enter. E.g. using this application on Ubuntu 18.04 64-bit only the Enter key starts the timeout. Considering that I have rewritten the application to start the timeout only when Enter is pressed. Below, I have demonstrated that my application shuts down with timeout on Ubuntu 18.04 64-bit in the same way as on Windows 7 64-bit.

Here is an example of how to publish the .net core application as a release and self-contained application using the dotnet publish command applied to the folder with the solution. Take a look at my example for Ubuntu:

dotnet publish -c Release -r ubuntu-x64 --self-contained true

To start the application on Ubuntu you need to apply using the terminal the following command chmod 777 ./app name giving permission to execute, see my example:

vadim@vadim-VirtualBox:~/Downloads/test core/publish$ chmod 777 ./ConsoleApp38

then you start the application:

vadim@vadim-VirtualBox:~/Downloads/test core/publish$ ./ConsoleApp38

Or you can use the dotnet command using the terminal for that:

dotnet ConsoleApp38.dll

On the screenshot below you can see that the application started on Ubuntu and after pressing Enter it shuts down within 5 seconds (in this version of the application I changed the timeout from 30 seconds to just 5).

enter image description here

Here is a more advanced version of this application that allowing to perform some tasks while the host is shutting down within the specified shutdown timeout after the command to stop is received. When the command to stop is received the main task is stopped. When the specified timeout is over the host is shut down regardless of whether there are some more tasks to perform within the shutdown timeout:

        using System;
        using System.Threading;
        using System.Threading.Tasks;
        using Microsoft.Extensions.Hosting;
        using Microsoft.Extensions.DependencyInjection;

        namespace ConsoleApp38
        {
            public class Program
            {
                public static async Task Main(string[] args)
                {
                    try
                    {
                        using (var host = new MyHost())
                        {
                            await host.StartAsync();
                            Console.WriteLine(@"Host has started. Please press Enter to stop the host");

                            while (true)
                                if (Console.KeyAvailable)
                                    if (Console.ReadKey(true).Key == ConsoleKey.Enter)
                                         await host.StopAsync();
                        }
                    }
                    catch (Exception e)
                    {
                        //log any exceptions here;
                    }
                }
            }

            public class MyHost : IHost
            {
                private static IHost _host;
                public MyHost()
                {
                    IHostBuilder builder = Host.CreateDefaultBuilder()
                        .ConfigureServices((hostContext, services) =>
                        {
                            services.Configure<HostOptions>(option =>
                            {
                                option.ShutdownTimeout = System.TimeSpan.FromSeconds(10);
                            });
                            services.AddHostedService<MyHostedService>();
                        });
                    _host = builder.Build();
                }

                public IServiceProvider Services
                {
                    get { return _host.Services; }
                }

                public void Dispose()
                {
                    _host.Dispose();
                }

                public async Task StartAsync(CancellationToken cancellationToken = default(CancellationToken))
                {
                    await _host.StartAsync(cancellationToken);
                }

                public async Task StopAsync(CancellationToken cancellationToken = default(CancellationToken))
                {
                    await _host.StopAsync(cancellationToken);
                }
            }

            public class MyHostedService : BackgroundService
            {
                public MyHostedService(IHostApplicationLifetime appLifetime)
                {
                    appLifetime.ApplicationStopping.Register(OnStopping);
                }
                
                public async Task StartAsync(CancellationToken cancellationToken)
                {
        
                }

                public async Task StopAsync(CancellationToken cancellationToken)
                {

                }

                private async void OnStopping()
                {
                    for (int i = 1; i <= 5; i++)
                    {
                        await Task.Delay(1000);
                        Console.WriteLine("Performing the shutdown task #: " + i);
                    }
                }

                protected override async Task ExecuteAsync(CancellationToken stoppingToken)
                {
                    while (true)
                    {
                        if (stoppingToken.IsCancellationRequested) continue;
                            await Task.Delay(2000);
                            Console.WriteLine("Doing a next step of work");
                    }
                }
            }
        }
V. S.
  • 1,086
  • 14
  • 14
0

If you just want to quit after 30 seconds you could try this approach:

using (host)
{
    var tokenSource = new CancellationTokenSource();
    tokenSource.CancelAfter(TimeSpan.FromSeconds(30)); 
    await host.RunAsync(tokenSource.Token);
}

This passes a CancellationToken that cancels after 30 seconds.

But rather use HostOptions and handle StopAsync in your IHosterService.

Preben Huybrechts
  • 5,853
  • 2
  • 27
  • 63