docker 容器立即退出,即使在 .NET Core 控制台应用程序中使用 Console.ReadLine()
docker container exits immediately even with Console.ReadLine() in a .NET Core console application
我正在尝试 运行 docker 容器内的 .NET Core 1.0.0 控制台应用程序。
当我从机器上的 Demo 文件夹中执行 运行 dotnet run
命令时,它工作正常;但是当 运行 使用 docker run -d --name demo Demo
时,容器会立即退出。
我尝试 docker logs demo
检查日志,它只显示来自 Console.WriteLine:
的文本
Demo app running...
仅此而已。
我已将项目上传到 https://github.com/learningdockerandnetcore/Demo
该项目包含 Programs.cs
、Dockerfile
用于创建演示图像和 project.json
文件。
您应该 运行 您的容器处于交互模式(使用 -i
选项),但请注意,当您 运行 容器时,后台进程将立即关闭,所以确保您的脚本 运行 在前台,否则根本无法运行。
我可以 Docker/Linux 让我的 .NET Core 应用程序保持活动状态的唯一方法是欺骗 ASP.NET 为我托管它...这太丑陋了!!
这样做会 运行 在 Docker 中使用 docker run -d
选项,因此您不必通过实时连接来保持 STDIN 流的活动。
我创建了一个 .NET Core 控制台应用程序(不是 ASP.NET 应用程序),我的程序 class 如下所示:
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();
}
}
}
创业公司class:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IServer, ConsoleAppRunner>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
}
}
ConsoleAppRunner class:
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 日
这个 post 似乎仍然很受欢迎 - 我想向任何阅读我的旧 post 的人指出它现在已经很古老了。我基于 .NET Core 1.1(当时是新的)。如果您使用的是较新版本 of.NET Core(2.0 / 2.1 或更高版本),那么现在可能有更好的方法来解决此问题。请花时间查看此线程中的其他一些 post,它们的排名可能不如这个,但可能更新和更新。
另一种“肮脏的方式”是在屏幕上使用以下方式启动您的程序:
screen -dmS yourprogramm
我不确定为什么 Console.ReadLine();
在分离的 docker 容器中 运行 .NET Core 控制台应用程序时不阻塞主线程,但最好的解决方案是在 Console.CancelKeyPress
事件中注册 ConsoleCancelEventHandler
。
然后您可以使用线程类型 WaitHandle
阻塞主线程,并在触发 Console.CancelKeyPress
时发出释放主线程的信号。
可以在此处找到一个很好的示例代码:https://gist.github.com/kuznero/73acdadd8328383ea7d5
更新:
在 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);
看到这个答案:
Is Thread.Sleep(Timeout.Infinite); more efficient than while(true){}?
改用 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"
如果您将应用程序切换到目标 .NET Core 2.0,则可以使用 Microsoft.Extensions.Hosting package to host a .NET Core console application by using the HostBuilder API to start/stop your application. Its ConsoleLifetime class 将处理一般应用程序 start/stop 方法。
为了 运行 您的应用程序,您应该实现自己的 IHostedService
接口或从 BackgroundService
class 继承,然后将其添加到主机上下文中 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();
}
}
我正在使用这种方法:
static async Task Main(string[] args)
{
// run code ..
await Task.Run(() => Thread.Sleep(Timeout.Infinite));
}
对于那些在 linux docker 中 运行 您的 .net 4.x 控制台应用程序无需指定 -i
并且想要 运行 它在后台,最好的解决方案是 mono.posix
包,它正是我们正在寻找的,听 linux 信号。
这也适用于 WebApi2
的 Owin
个项目,或者基本上任何 console app
对于我们大多数人来说 运行使用 console.read
或 ManualResetEventSlim
或 AutoResetEvent
在后台运行容器将无法工作,因为 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)
};
}
}
我正在尝试 运行 docker 容器内的 .NET Core 1.0.0 控制台应用程序。
当我从机器上的 Demo 文件夹中执行 运行 dotnet run
命令时,它工作正常;但是当 运行 使用 docker run -d --name demo Demo
时,容器会立即退出。
我尝试 docker logs demo
检查日志,它只显示来自 Console.WriteLine:
Demo app running...
仅此而已。
我已将项目上传到 https://github.com/learningdockerandnetcore/Demo
该项目包含 Programs.cs
、Dockerfile
用于创建演示图像和 project.json
文件。
您应该 运行 您的容器处于交互模式(使用 -i
选项),但请注意,当您 运行 容器时,后台进程将立即关闭,所以确保您的脚本 运行 在前台,否则根本无法运行。
我可以 Docker/Linux 让我的 .NET Core 应用程序保持活动状态的唯一方法是欺骗 ASP.NET 为我托管它...这太丑陋了!!
这样做会 运行 在 Docker 中使用 docker run -d
选项,因此您不必通过实时连接来保持 STDIN 流的活动。
我创建了一个 .NET Core 控制台应用程序(不是 ASP.NET 应用程序),我的程序 class 如下所示:
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();
}
}
}
创业公司class:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IServer, ConsoleAppRunner>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
}
}
ConsoleAppRunner class:
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 日
这个 post 似乎仍然很受欢迎 - 我想向任何阅读我的旧 post 的人指出它现在已经很古老了。我基于 .NET Core 1.1(当时是新的)。如果您使用的是较新版本 of.NET Core(2.0 / 2.1 或更高版本),那么现在可能有更好的方法来解决此问题。请花时间查看此线程中的其他一些 post,它们的排名可能不如这个,但可能更新和更新。
另一种“肮脏的方式”是在屏幕上使用以下方式启动您的程序:
screen -dmS yourprogramm
我不确定为什么 Console.ReadLine();
在分离的 docker 容器中 运行 .NET Core 控制台应用程序时不阻塞主线程,但最好的解决方案是在 Console.CancelKeyPress
事件中注册 ConsoleCancelEventHandler
。
然后您可以使用线程类型 WaitHandle
阻塞主线程,并在触发 Console.CancelKeyPress
时发出释放主线程的信号。
可以在此处找到一个很好的示例代码:https://gist.github.com/kuznero/73acdadd8328383ea7d5
更新: 在 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);
看到这个答案:
Is Thread.Sleep(Timeout.Infinite); more efficient than while(true){}?
改用 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"
如果您将应用程序切换到目标 .NET Core 2.0,则可以使用 Microsoft.Extensions.Hosting package to host a .NET Core console application by using the HostBuilder API to start/stop your application. Its ConsoleLifetime class 将处理一般应用程序 start/stop 方法。
为了 运行 您的应用程序,您应该实现自己的 IHostedService
接口或从 BackgroundService
class 继承,然后将其添加到主机上下文中 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();
}
}
我正在使用这种方法:
static async Task Main(string[] args)
{
// run code ..
await Task.Run(() => Thread.Sleep(Timeout.Infinite));
}
对于那些在 linux docker 中 运行 您的 .net 4.x 控制台应用程序无需指定 -i
并且想要 运行 它在后台,最好的解决方案是 mono.posix
包,它正是我们正在寻找的,听 linux 信号。
这也适用于 WebApi2
的 Owin
个项目,或者基本上任何 console app
对于我们大多数人来说 运行使用 console.read
或 ManualResetEventSlim
或 AutoResetEvent
在后台运行容器将无法工作,因为 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)
};
}
}