简体   繁体   English

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

[英]docker container exits immediately even with Console.ReadLine() in a .NET Core console application

I am trying to run a .NET Core 1.0.0 console application inside a docker container.我正在尝试在 docker 容器中运行 .NET Core 1.0.0 控制台应用程序。
When I run dotnet run command from inside the Demo folder on my machine, it works fine;当我从机器上的 Demo 文件夹中运行dotnet run命令时,它工作正常; but when run using docker run -d --name demo Demo , the container exits immediately.但是当使用docker run -d --name demo Demo时,容器会立即退出。

I tried docker logs demo to check the logs and it just shows the text from the Console.WriteLine:我尝试使用docker logs demo来检查日志,它只显示来自 Console.WriteLine 的文本:

Demo app running...演示应用程序正在运行...

and nothing else.没有别的。

I have uploaded the project at https://github.com/learningdockerandnetcore/Demo我已经在https://github.com/learningdockerandnetcore/Demo上传了项目

The project contains Programs.cs , Dockerfile used to create Demo image, and project.json file.该项目包含Programs.cs 、用于创建 Demo 镜像的Dockerfileproject.json文件。

If you switch your app to target .NET Core 2.0, you can use the Microsoft.Extensions.Hosting package to host a .NET Core console application by using the HostBuilder API to start/stop your application.如果您将应用程序切换到面向 .NET Core 2.0,则可以使用Microsoft.Extensions.Hosting包通过使用 HostBuilder API 来启动/停止应用程序来托管 .NET Core 控制台应用程序。 Its ConsoleLifetime class would process the general application start/stop method.它的ConsoleLifetime类将处理一般的应用程序启动/停止方法。

In order to run your app, you should implement your own IHostedService interface or inherit from the BackgroundService class, then add it to host context within ConfigureServices .为了运行您的应用程序,您应该实现自己的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);
    }
}

Here's a sample hosted service:这是一个示例托管服务:

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

Then creating the HostBuilder and adding the service and other components (logging, configuration).然后创建 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();
    }
}

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

The only way I could get Docker/Linux to keep my .NET Core application alive was to spoof ASP.NET into hosting it for me... This is such an ugly hack!!我能让 Docker/Linux 保持我的 .NET Core 应用程序存活的唯一方法是欺骗 ASP.NET 为我托管它......这是一个如此丑陋的黑客!!

Doing it this way will run in Docker using the docker run -d option, so you don't have to have a live connection to keep the STDIN stream alive.这样做将使用docker run -d选项在 Docker 中docker run -d ,因此您不必具有实时连接来保持 STDIN 流处于活动状态。

I created a .NET Core console application (not an ASP.NET app) and my Program class looks like this:我创建了一个 .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();
        }
    }      
}

The Startup class:启动类:

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


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

The ConsoleAppRunner class: 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.
    }
}

The only nice thing about it is that you get to use DI in your application (if you want to) - so in my use case, I am using the ILoggingFactory to handle my logging.唯一的好处是你可以在你的应用程序中使用 DI(如果你愿意的话)——所以在我的用例中,我使用 ILoggingFactory 来处理我的日志记录。

Edit 30th Oct 2018编辑 2018 年 10 月 30 日
This post still seems to be popular - I'd like to just point out to anyone reading my old post that it is now pretty ancient.这篇文章似乎仍然很受欢迎——我想向任何阅读我的旧文章的人指出它现在已经很古老了。 I was basing it on .NET Core 1.1 (which was new at the time).我基于 .NET Core 1.1(当时是新的)。 It is likely that if you are using a newer version of.NET Core (2.0 / 2.1 or greater) that there is probably a much better way of solving this problem now.如果您使用的是较新版本的 .NET Core(2.0 / 2.1 或更高版本),那么现在可能有更好的方法来解决此问题。 Please take time to look at some of the other posts on this thread which may not be as highly ranked as this one, but may be newer and more up-to-date.请花点时间查看此线程上的其他一些帖子,这些帖子的排名可能不如该帖子高,但可能更新和更新。

You can use:您可以使用:

Thread.Sleep(Timeout.Infinite);

See this answer:看到这个答案:

Is Thread.Sleep(Timeout.Infinite); 是 Thread.Sleep(Timeout.Infinite); more efficient than while(true){}? 比 while(true){} 更有效率?

I am not sure why Console.ReadLine();我不知道为什么Console.ReadLine(); doesn't block the main thread when running a .NET Core console app in a detached docker container, but the best solution is to register a ConsoleCancelEventHandler with the Console.CancelKeyPress event.在分离的ConsoleCancelEventHandler容器中运行 .NET Core 控制台应用程序时,不会阻塞主线程,但最好的解决方案是使用Console.CancelKeyPress事件注册ConsoleCancelEventHandler

Then you can instead block the main thread with a type of Threading WaitHandle and signal the release of the main thread when Console.CancelKeyPress is fired.然后,您可以改为使用 Threading WaitHandle类型阻塞主线程,并在触发Console.CancelKeyPress时发出主线程释放的信号。

A good example code can be found here: https://gist.github.com/kuznero/73acdadd8328383ea7d5一个很好的示例代码可以在这里找到: https : //gist.github.com/kuznero/73acdadd8328383ea7d5

I'm using this approach:我正在使用这种方法:

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

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

另一种“肮脏的方式”是使用以下命令在屏幕中启动您的程序:

screen -dmS yourprogramm

For those that what to run your .net 4.x console app in linux docker without having to specified -i and want to run it in the background, the best solution is mono.posix package, which does exactly what we are looking for, listen to linux signals.对于那些在 linux docker 中运行 .net 4.x 控制台应用程序而不必指定-i并希望在后台运行它的人来说,最好的解决方案是mono.posix包,它正是我们正在寻找的,听linux信号。

this also applys to WebApi2 with Owin projects, or basically any console app这也适用于带有Owin项目的WebApi2 ,或者基本上任何console app

for most of us running containers in the background using console.read or ManualResetEventSlim or AutoResetEvent wont worked because of detached mode by docker.对于我们中的大多数人来说,使用console.readManualResetEventSlimAutoResetEvent在后台运行容器将无法工作,因为AutoResetEvent的分离模式。

The best solution is installing Install-Package Mono.Posix最好的解决方案是安装Install-Package Mono.Posix

here's an example:这是一个例子:

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

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

Using Console.ReadLine instead seems to work.改用Console.ReadLine似乎有效。

C#: C#:

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

F#: F#:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM