簡體   English   中英

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

[英]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 的文本:

演示應用程序正在運行...

沒有別的。

我已經在https://github.com/learningdockerandnetcore/Demo上傳了項目

該項目包含Programs.cs 、用於創建 Demo 鏡像的Dockerfileproject.json文件。

如果您將應用程序切換到面向 .NET Core 2.0,則可以使用Microsoft.Extensions.Hosting包通過使用 HostBuilder API 來啟動/停止應用程序來托管 .NET Core 控制台應用程序。 它的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();
    }
}

您應該以交互模式運行容器(使用-i選項),但請注意,當您運行容器時,后台進程將立即關閉,因此請確保您的腳本在前台運行,否則它根本無法工作.

我能讓 Docker/Linux 保持我的 .NET Core 應用程序存活的唯一方法是欺騙 ASP.NET 為我托管它......這是一個如此丑陋的黑客!!

這樣做將使用docker run -d選項在 Docker 中docker run -d ,因此您不必具有實時連接來保持 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 或更高版本),那么現在可能有更好的方法來解決此問題。 請花點時間查看此線程上的其他一些帖子,這些帖子的排名可能不如該帖子高,但可能更新和更新。

您可以使用:

Thread.Sleep(Timeout.Infinite);

看到這個答案:

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

我不知道為什么Console.ReadLine(); 在分離的ConsoleCancelEventHandler容器中運行 .NET Core 控制台應用程序時,不會阻塞主線程,但最好的解決方案是使用Console.CancelKeyPress事件注冊ConsoleCancelEventHandler

然后,您可以改為使用 Threading WaitHandle類型阻塞主線程,並在觸發Console.CancelKeyPress時發出主線程釋放的信號。

一個很好的示例代碼可以在這里找到: https : //gist.github.com/kuznero/73acdadd8328383ea7d5

我正在使用這種方法:

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

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

另一種“骯臟的方式”是使用以下命令在屏幕中啟動您的程序:

screen -dmS yourprogramm

對於那些在 linux docker 中運行 .net 4.x 控制台應用程序而不必指定-i並希望在后台運行它的人來說,最好的解決方案是mono.posix包,它正是我們正在尋找的,聽linux信號。

這也適用於帶有Owin項目的WebApi2 ,或者基本上任何console app

對於我們中的大多數人來說,使用console.readManualResetEventSlimAutoResetEvent在后台運行容器將無法工作,因為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

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM