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

时间:2016-07-24 05:22:30

标签: c# docker asp.net-core dockerfile .net-core

我正在尝试在docker容器中运行.net核心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文件。

9 个答案:

答案 0 :(得分:12)

如果您将应用切换到目标.net core 2.0,则可以使用Microsoft.Extensions.Hosting pacakge通过使用HostBuilder API来启动/停止应用程序来托管.net核心控制台应用程序。它的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();
    }
}

然后创建HostBuilder并添加服务和其他组件(日志记录,配置)。

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();
    }
}

答案 1 :(得分:10)

我能让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();
        }
    }      
}

Startup类:

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来处理我的日志记录。

编辑2018年10月30日 这篇文章似乎仍然很受欢迎 - 我只想指出任何人阅读我的旧帖子,它现在已经很古老了。我的基础是.NET核心1.1(当时是新的)。如果你使用更新版本的.NET核心(2.0 / 2.1或更高版本),那么现在可能有更好的方法来解决这个问题。请花点时间看一下这个帖子中的其他一些帖子,这些帖子可能没有这个帖子那么高,但可能更新,更新。

答案 2 :(得分:8)

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

答案 3 :(得分:1)

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

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

可在此处找到一个好的示例代码:https://gist.github.com/kuznero/73acdadd8328383ea7d5

答案 4 :(得分:1)

您可以使用:

Thread.Sleep(Timeout.Infinite);

看到这个答案:

Is Thread.Sleep(Timeout.Infinite); more efficient than while(true){}?

答案 5 :(得分:0)

另一个“肮脏的方式”是使用

在屏幕上启动你的程序
screen -dmS yourprogramm

答案 6 :(得分:0)

我正在使用这种方法:

static async Task Main(string[] args)
{
   // run code ..

   await Task.Run(() => Thread.Sleep(Timeout.Infinite));
}

答案 7 :(得分:0)

对于那些无需指定-i就可以在linux docker中运行.net 4.x控制台应用程序而又想在后台运行的应用程序,最好的解决方案是mono.posix软件包,该软件包正是我们想要的,听linux信号。

这也适用于具有WebApi2个项目的Owin或基本上任何console app

对于大多数我们使用console.readManualResetEventSlimAutoResetEvent在后​​台运行容器的用户来说,由于docker的分离模式而无法正常工作。

最好的解决方案是安装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

答案 8 :(得分:-2)

使用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"