【问题标题】:docker container exits immediately even with Console.ReadLine() in a .NET Core console application即使在 .NET Core 控制台应用程序中使用 Console.ReadLine(),docker 容器也会立即退出
【发布时间】:2016-11-27 16:32:18
【问题描述】:

我正在尝试在 docker 容器内运行 .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用于创建Demo镜像,project.json文件。

【问题讨论】:

  • 我正在努力学习自己:我认为您想在交互模式下运行它并且可能想添加一个术语。 docker run -it --name demo Demo
  • 如果您在后台模式下运行它(-d),您也可以附加到它docker attach {container} 以返回它。您不会看到输出,因为它已经输出了,但是您可以按 Enter 键让容器退出

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


【解决方案1】:

您应该在交互模式下运行您的容器(使用-i 选项),但请注意,当您运行容器时,后台进程将立即关闭,因此请确保您的脚本在前台运行或简单地运行不会工作。

【讨论】:

  • 不是-i 交互模式 - 不是守护(或分离)模式吗?
  • 是的,-d 是守护/分离模式。
  • -i 表示 Docker 为进程提供了 StandardInput。这与分离模式无关。如果不存在 StandardInput,Console.ReadLine 的默认行为是返回 null 而不是阻塞。
【解决方案2】:

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

2018 年 10 月 30 日编辑
这篇文章似乎仍然很受欢迎——我想向任何阅读我的旧文章的人指出,它现在已经很古老了。我基于 .NET Core 1.1(当时是新的)。如果您使用的是较新版本的 .NET Core(2.0 / 2.1 或更高版本),现在可能有更好的方法来解决此问题。请花点时间查看此线程上的其他一些帖子,这些帖子的排名可能没有这个高,但可能更新和更新。

【讨论】:

    【解决方案3】:

    另一种“肮脏的方式”是在屏幕中启动程序:

    screen -dmS yourprogramm
    

    【讨论】:

      【解决方案4】:

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

      然后,您可以改为使用 Threading WaitHandle 类型阻塞主线程,并在触发 Console.CancelKeyPress 时发出释放主线程的信号。

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

      【讨论】:

        【解决方案5】:

        更新: 在 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>(); });
        }
        

        worker 继承自 IHostedService

        当你关闭你的应用程序时,这个方法也会监听来自 docker 的正确信号。


        旧答案 您可以使用:

        Thread.Sleep(Timeout.Infinite);
        

        看到这个答案:

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

        【讨论】:

          【解决方案6】:

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

          【讨论】:

          • 这不起作用,因为如果没有 StandardInput 存在(在非交互式 docker 容器中运行时可能会出现这种情况)Console.ReadLine() 将返回 null(无阻塞),.Trim()会抛出异常。
          【解决方案7】:

          如果您将应用切换到 .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();
              }
          }
          

          然后创建 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();
              }
          }
          

          【讨论】:

          • 很棒的帖子;非常感谢。
          • 很好的答案。还有一件事如何通过服务帐户运行此控制台应用程序?我在 Linxu 容器上部署它
          • 不确定服务帐户。这个控制台应用可以在 windows 和 Linux 容器上运行
          • @FeiyuZhou 是的,它在 Linux 容器上运行没有任何问题。我的意思是将应用程序的用户设置为服务帐户。目前它默认运行的用户无权在我们的 fileSharer 服务器上写入。有什么想法吗?
          • @gauravthakur 也许您可以尝试将其作为 Windows 服务运行:ben-morris.com/…
          【解决方案8】:

          我正在使用这种方法:

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

          【讨论】:

            【解决方案9】:

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

            这也适用于WebApi2Owin 项目,或者基本上任何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

            【讨论】:

              猜你喜欢
              • 2014-09-04
              • 1970-01-01
              • 1970-01-01
              • 2012-09-13
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-02-01
              相关资源
              最近更新 更多