【问题标题】:Keep Dotnet Core Grpc Server running as a console application?保持 Dotnet Core Grpc Server 作为控制台应用程序运行?
【发布时间】:2018-02-09 20:44:23
【问题描述】:

我试图让 Grpc 服务器作为控制台守护进程运行。这个 gRPC 服务器是一个在 docker 容器中运行的微服务。

我能找到的所有示例都使用以下内容:

Console.ReadKey();

这确实阻塞了主线程并使其保持运行,但在 docker 中不起作用,并出现以下错误:

"Cannot read keys when either application does not have a console or when console input has been redirected. Try Console.Read."

现在我可能会尝试专门为 docker 找到一种解决方法,但这仍然感觉不对。有谁知道保持服务运行的良好“生产就绪”方式?

【问题讨论】:

  • 如果你在启动 docker 容器时使用-it 就可以了。如果这对您不起作用,您可以使用 Thread.Sleep(Timeout.Infinite) 无限期地休眠主线程
  • Docker 容器部署到云平台的 Kubernetes 集群中。交互式容器不是一种选择。 Thread.Sleep 的问题是,例如,当容器停止时,grpc 服务器没有正常关闭。

标签: c# docker .net-core grpc


【解决方案1】:

您现在可以使用 Microsoft.Extensions.Hosting pacakge,它是 asp.net 核心和控制台应用程序的托管和启动基础架构。

与 asp.net core 一样,您可以使用 HostBuilder API 开始构建 gRPC 主机并进行设置。以下代码是为了让控制台应用程序一直运行直到它停止(例如使用 Control-C):

using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;

public class Program
{
    public static async Task Main(string[] args)
    {
        var hostBuilder = new HostBuilder();

         // register your configuration and services.
        ....

        await hostBuilder.RunConsoleAsync();
    }
}

为了运行 gRPC 服务,您需要在托管服务中启动/停止 Grpc.Core.Server。托管服务基本上是在主机本身启动时由主机运行的一段代码,在它停止时也是如此。这在 IHostedService 接口中表示。也就是说,实现一个 GrpcHostedService 来覆盖接口:

using System.Threading;
using System.Threading.Tasks;
using Grpc.Core;
using Microsoft.Extensions.Hosting;

namespace Grpc.Host
{
    public class GrpcHostedService: IHostedService
    {
        private Server _server;

        public GrpcHostedService(Server server)
        {
            _server = server;
        }

        public Task StartAsync(CancellationToken cancellationToken)
        {
            _server.Start();
            return Task.CompletedTask;
        }

        public async Task StopAsync(CancellationToken cancellationToken) => await _server.ShutdownAsync();
    }
}

真的很简单。我们通过依赖注入获得一个GrpcHostedService 实例,并在主机启动时在其上运行 StartAsync。当主机停止时,我们运行 StopAsync 以便我们可以优雅地关闭所有东西,包括 Grpc 服务器。

然后回到Program.cs并进行一些更改:

public class Program
{
    public static async Task Main(string[] args)
    {
        var hostBuilder = new HostBuilder()
             // Add configuration, logging, ...
            .ConfigureServices((hostContext, services) =>
            {
                // Better to use Dependency Injection for GreeterImpl
                Server server = new Server
                {
                    Services = {Greeter.BindService(new GreeterImpl())},
                    Ports = {new ServerPort("localhost", 5000, ServerCredentials.Insecure)}
                };
                services.AddSingleton<Server>(server);
                services.AddSingleton<IHostedService, GrpcHostedService>();
            });

        await hostBuilder.RunConsoleAsync();
    }
}

通过这样做,通用主机将自动在我们的托管服务上运行 StartAsync,这反过来又会在 Server 实例上调用 StartAsync,实质上是启动 gRPC 服务器。

当我们使用 Control-C 关闭主机时,通用主机将自动在我们的托管服务上调用 StopAsync,该服务将再次在 Server 实例上调用 StopAsync,这将进行一些清理。

HostBuilder中的其他配置可以看这个blog

【讨论】:

  • 嘿,这在 .net core 2.0 或仅 2.1 中可用=
  • 这在 .net core 2.0 中可用,而包 Microsoft.Extensions.Hosting 版本是 2.1.0-preview1-final 和 LangVersion 是 7.1 以便使用异步主
  • 所以现在我们可以在控制台模式下运行这个服务,这意味着当我关闭控制台时,我的服务就消失了。有没有办法使用微软组件而不是使用例如在 Windows 服务中运行它。 dasMulli/dotnet-win32-service?
  • 我在 docker(主要是 Linux docker)中运行控制台应用程序。使用HostBuilder 的好处是无需额外代码(Console.ReadKeyManualResetEvent)即可托管该服务,并且它支持正常关闭。不确定在 Windows 服务中运行它。但是由于 asp.net core 可以通过一些扩展代码来实现,所以控制台应用程序也可以托管在 Windows 服务中。看到这个:docs.microsoft.com/en-us/aspnet/core/host-and-deploy/…
  • 似乎 ASP.NET 团队已经实现了它:github.com/aspnet/Hosting/blob/dev/samples/GenericHostSample/… 猜猜它会在 2.1 中发布
【解决方案2】:

使用ManualResetEvent 阻塞主线程,直到收到关闭事件。

例如在一个琐碎的情况下:

class Program
{
  public static ManualResetEvent Shutdown = new ManualResetEvent(false);

  static void Main(string[] args)
  {
    Task.Run(() =>
    {
      Console.WriteLine("Waiting in other thread...");
      Thread.Sleep(2000);
      Shutdown.Set();
    });

    Console.WriteLine("Waiting for signal");
    Shutdown.WaitOne();

    Console.WriteLine("Resolved");
  }
}

例如,在您的情况下,我想像:

using System;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Grpc.Core;
using Helloworld;

namespace GreeterServer
{
  class GreeterImpl : Greeter.GreeterBase
  {
    // Server side handler of the SayHello RPC
    public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
    {
      Program.Shutdown.Set(); // <--- Signals the main thread to continue 
      return Task.FromResult(new HelloReply {Message = "Hello " + request.Name});
    }
  }

  class Program
  {
    const int Port = 50051;

    public static ManualResetEvent Shutdown = new ManualResetEvent(false);

    public static void Main(string[] args)
    {
      Server server = new Server
      {
        Services = {Greeter.BindService(new GreeterImpl())},
        Ports = {new ServerPort("localhost", Port, ServerCredentials.Insecure)}
      };
      server.Start();

      Shutdown.WaitOne(); // <--- Waits for ever or signal received

      server.ShutdownAsync().Wait();
    }
  }
}

【讨论】:

  • 也可以使用async Main 并改用TaskCompletionSource 对象,这将释放主线程以进行其他工作。
  • 既然可以server.ShutdownTask.Wait();,为什么还要麻烦设置一个单独的事件?这将等到服务器本身关闭(关闭服务是另一回事,但应该通过AssemblyLoadContext.Default.Unloading 事件自动处理,或者您可以自己覆盖和关闭服务。
猜你喜欢
  • 1970-01-01
  • 2018-10-17
  • 2010-10-16
  • 2010-11-17
  • 2018-02-16
  • 2019-11-26
  • 2011-02-04
  • 1970-01-01
  • 2017-08-17
相关资源
最近更新 更多