docker 容器会立即退出,即使在 .NET Core 控制台应用程序中使用 Console.ReadLine() 也

本文关键字:应用程序 Console ReadLine 控制台 NET 退出 docker Core | 更新日期: 2023-09-27 17:58:19

我正在尝试在docker container中运行.NET Core 1.0.0控制台应用程序.
当我从机器上的 Demo 文件夹中运行dotnet run命令时,它工作正常;但是当使用docker run -d --name demo Demo运行时,容器会立即退出。

我尝试docker logs demo检查日志,它只显示来自 Console.WriteLine 的文本:

演示应用正在运行...

仅此而已。

我已将项目上传到 https://github.com/learningdockerandnetcore/Demo

该项目包含Programs.csDockerfile用于创建演示映像和project.json文件。

docker 容器会立即退出,即使在 .NET Core 控制台应用程序中使用 Console.ReadLine() 也

如果将应用切换到面向 .NET Core 2.0,则可以使用 Microsoft.Extensions.Hosting 包来托管 .NET Core 控制台应用程序,方法是使用 HostBuilder API 启动/停止应用程序。它的 ConsoleLifetime 类将处理常规应用程序启动/停止方法。

为了运行您的应用程序,您应该实现自己的 IHostedService 接口或从 BackgroundService 类继承,然后将其添加到 ConfigureServices 中的主机上下文中。

namespace Microsoft.Extensions.Hosting
{
    //
    // Summary:
    //     Defines methods for objects that are managed by the host.
    public interface IHostedService
    {
        // Summary:
        // Triggered when the application host is ready to start the service.
        Task StartAsync(CancellationToken cancellationToken);
        // Summary:
        // Triggered when the application host is performing a graceful shutdown.
        Task StopAsync(CancellationToken cancellationToken);
    }
}

下面是一个示例托管服务:

public class TimedHostedService : IHostedService, IDisposable
{
    private readonly ILogger _logger;
    private Timer _timer;
    public TimedHostedService(ILogger<TimedHostedService> logger)
    {
        _logger = logger;
    }
    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Background Service is starting.");
        _timer = new Timer(DoWork, null, TimeSpan.Zero, 
            TimeSpan.FromSeconds(5));
        return Task.CompletedTask;
    }
    private void DoWork(object state)
    {
        _logger.LogInformation("Timed Background Service is working.");
    }
    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Background Service is stopping.");
        _timer?.Change(Timeout.Infinite, 0);
        return Task.CompletedTask;
    }
    public void Dispose()
    {
        _timer?.Dispose();
    }
}

然后创建主机构建器并添加服务和其他组件(日志记录、配置(。

public class Program
{
    public static async Task Main(string[] args)
    {
        var hostBuilder = new HostBuilder()
             // Add configuration, logging, ...
            .ConfigureServices((hostContext, services) =>
            {
                // Add your services with depedency injection.
            });
        await hostBuilder.RunConsoleAsync();
    }
}

您应该在交互模式下运行容器(使用 -i 选项(,但请注意,运行容器时后台进程将立即关闭,因此请确保您的脚本在前台运行,否则它根本无法工作。

我能让 Docker/Linux 保持我的 .NET Core 应用程序存活的唯一方法是欺骗 ASP.NET 为我托管它......这真是个丑陋的黑客!!

这样做将使用 docker run -d 选项在 Docker 中运行,因此您不必有实时连接来保持 STDIN 流的活动。

我创建了一个 .NET Core 控制台应用程序(不是 ASP.NET 应用程序(,我的 Program 类如下所示:

public class Program
{
    public static ManualResetEventSlim Done = new ManualResetEventSlim(false);
    public static void Main(string[] args)
    {
        //This is unbelievably complex because .NET Core Console.ReadLine() does not block in a docker container...!
        var host = new WebHostBuilder().UseStartup(typeof(Startup)).Build();
        
        using (CancellationTokenSource cts = new CancellationTokenSource())
        {
            Action shutdown = () =>
            {
                if (!cts.IsCancellationRequested)
                {
                    Console.WriteLine("Application is shutting down...");
                    cts.Cancel();
                }
                Done.Wait();
            };
            Console.CancelKeyPress += (sender, eventArgs) =>
            {
                shutdown();
                // Don't terminate the process immediately, wait for the Main thread to exit gracefully.
                eventArgs.Cancel = true;
            };
            host.Run(cts.Token);
            Done.Set();
        }
    }      
}

启动类:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<IServer, ConsoleAppRunner>();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
    }
}

ConsoleAppRunner 类:

public class ConsoleAppRunner : IServer
{
    /// <summary>A collection of HTTP features of the server.</summary>
    public IFeatureCollection Features { get; }
    public ConsoleAppRunner(ILoggerFactory loggerFactory)
    {
        Features = new FeatureCollection();
    }
    /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
    public void Dispose()
    {
    }
    /// <summary>Start the server with an application.</summary>
    /// <param name="application">An instance of <see cref="T:Microsoft.AspNetCore.Hosting.Server.IHttpApplication`1" />.</param>
    /// <typeparam name="TContext">The context associated with the application.</typeparam>
    public void Start<TContext>(IHttpApplication<TContext> application)
    {
        //Actual program code starts here...
        Console.WriteLine("Demo app running...");
        Program.Done.Wait();        // <-- Keeps the program running - The Done property is a ManualResetEventSlim instance which gets set if someone terminates the program.
    }
}

它唯一的好处是你可以在你的应用程序中使用 DI(如果你愿意的话( - 所以在我的用例中,我正在使用 ILoggingFactory 来处理我的日志记录。

编辑 30th 十月 2018<</strong>br/>这篇文章似乎仍然很受欢迎 - 我想向任何阅读我的旧帖子的人指出,它现在非常古老。我基于.NET Core 1.1(当时是新的(。如果您使用的是较新版本 of.NET Core(2.0/2.1或更高版本(,那么现在可能有更好的方法来解决此问题。请花点时间看看这个帖子上的其他一些帖子,这些帖子的排名可能不如这个帖子高,但可能是更新和最新的。

更新:在 dotnetcore> 2.0 中,您可以使用更好的方法来保持应用运行。在 dotnet 5 中,可以执行以下操作:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }
    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureServices((hostContext, services) => { services.AddHostedService<Worker>(); });
}

其中工人从IHostedService继承。

此方法还将在关闭应用程序时侦听来自 docker 的正确信号。

<小时 />

旧答案您可以使用:

Thread.Sleep(Timeout.Infinite);

看到这个答案:

Thread.Sleep(Timeout.Infinite(是否比while(true({}更有效率?

我正在使用这种方法:

static async Task Main(string[] args)
{
   // run code ..
   await Task.Run(() => Thread.Sleep(Timeout.Infinite));
}

我不确定为什么Console.ReadLine();在分离的 docker 容器中运行 .NET Core 控制台应用程序时不阻止主线程,但最好的解决方案是向 Console.CancelKeyPress 事件注册ConsoleCancelEventHandler

然后,您可以使用一种线程WaitHandle来阻止主线程,并在触发Console.CancelKeyPress时发出释放主线程的信号。

一个很好的示例代码可以在这里找到:https://gist.github.com/kuznero/73acdadd8328383ea7d5

对于那些在

linux docker 中运行 .net 4.x 控制台应用程序而无需指定-i并希望在后台运行它的人来说,最好的解决方案是 mono.posix 包,它完全符合我们的要求,侦听 Linux 信号。

这也适用于Owin项目的WebApi2,或者基本上任何console app

对于我们大多数人来说,使用 console.readManualResetEventSlim 在后台运行容器,或者由于 Docker 的分离模式AutoResetEvent不起作用。

最好的解决方案是安装Install-Package Mono.Posix

下面是一个示例:

using System;
using Microsoft.Owin.Hosting;
using Mono.Unix;
using Mono.Unix.Native;
public class Program
{
    public static void Main(string[] args)
    {
        string baseAddress = "http://localhost:9000/"; 
        // Start OWIN host 
        using (WebApp.Start<Startup>(url: baseAddress)) 
        { 
            Console.ReadLine(); 
        }
        if (IsRunningOnMono())
        {
            var terminationSignals = GetUnixTerminationSignals();
            UnixSignal.WaitAny(terminationSignals);
        }
        else
        {
            Console.ReadLine();
        }
        host.Stop();
    }
    public static bool IsRunningOnMono()
    {
        return Type.GetType("Mono.Runtime") != null;
    }
    public static UnixSignal[] GetUnixTerminationSignals()
    {
        return new[]
        {
            new UnixSignal(Signum.SIGINT),
            new UnixSignal(Signum.SIGTERM),
            new UnixSignal(Signum.SIGQUIT),
            new UnixSignal(Signum.SIGHUP)
        };
    }
}

完整来源博客文章:https://dusted.codes/running-nancyfx-in-a-docker-container-a-beginners-guide-to-build-and-run-dotnet-applications-in-docker

另一种"肮脏的方法"是使用以下方法在屏幕中启动程序:

screen -dmS yourprogramm

改用Console.ReadLine似乎有效。

C#:

do
{
    Console.WriteLine($"Type: quit<Enter> to end {Process.GetCurrentProcess().ProcessName}");
}
while (!Console.ReadLine().Trim().Equals("quit",StringComparison.OrdinalIgnoreCase));

F#:

while not (Console.ReadLine().Trim().Equals("quit",StringComparison.OrdinalIgnoreCase)) do
    printfn "Type: quit<Enter> to end"