问题 让Dotnet Core Grpc Server作为控制台应用程序运行?


我试图让一个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找一个解决方法,但这仍然感觉不对。有谁知道一个良好的“生产就绪”方式来保持服务运行?


2406
2017-08-31 19:55


起源

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


答案:


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

与asp.net核心一样,您可以使用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,而后者又会在上面调用StartAsync Server 实例,基本上启动gRPC服务器。

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

对于HostBuilder中的其他配置,您可以看到这一点 博客


8
2018-03-12 10:59



嘿,这可以在.net core 2.0或者只有2.1 = <? - UnderNotic
这在.net core 2.0中可用,而Microsoft.Extensions.Hosting包是2.1.0-preview1-final,而LangVersion是7.1以便使用async main - Feiyu Zhou
所以现在我们可以在控制台模式下运行这项服务,这意味着当我加入关闭控制台时,我的服务就消失了。有没有办法在使用微软组件而不是使用例如微软组件的情况下在Windows服务中运行它。 dasMulli / DOTNET-Win32的服务? - Remco
我在docker中运行控制台应用程序(主要是Linux docker)。使用的好处 HostBuilder 是否可以托管服务而无需额外的代码(Console.ReadKey 要么 ManualResetEvent)它支持正常关闭。不确定在Windows服务中运行它。但由于asp.net核心可以通过一些扩展代码实现这一点,因此控制台应用程序也可以托管在Windows服务中。看到这个: docs.microsoft.com/en-us/aspnet/core/host-and-deploy/... - Feiyu Zhou
似乎ASP.NET团队已经实现了它: github.com/aspnet/Hosting/blob/dev/samples/GenericHostSample/...  猜猜它将在2.1中发布 - Feiyu Zhou


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

5
2017-09-01 01:43



试试这个,并会让你更新。谢谢! - Mitch Dart
一个人也可以使用 async Main 并使用 TaskCompletionSource 而不是对象,这将释放主线程以进行其他一些工作。 - VMAtm
为什么在你可以做的时候经历所有设置单独事件的麻烦 server.ShutdownTask.Wait();?这将等到服务器本身关闭(关闭服务是一个单独的事情,但应该通过自动处理自动处理 AssemblyLoadContext.Default.Unloading  事件或您可以自己覆盖和关闭服务。 - Mr. T