保持Dotnet Core Grpc Server作为控制台应用程序运行?

Mit*_*art 12 c# docker .net-core grpc

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

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

Console.ReadKey();
Run Code Online (Sandbox Code Playgroud)

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

"Cannot read keys when either application does not have a console or when console input has been redirected. Try Console.Read."
Run Code Online (Sandbox Code Playgroud)

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

Fei*_*hou 15

您现在可以使用Microsoft.Extensions.Hostingpacakge,它是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();
    }
}
Run Code Online (Sandbox Code Playgroud)

要运行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();
    }
}
Run Code Online (Sandbox Code Playgroud)

这真的很简单.我们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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

对于HostBuilder中的其他配置,您可以看到此博客.


Dou*_*oug 5

使用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");
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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();
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 当您只需执行“server.ShutdownTask.Wait();”即可时,为什么要费尽心思去设置一个单独的事件呢?这将等到服务器本身关闭(关闭服务是另一回事,但应该通过“AssemblyLoadContext.Default.Unloading”事件自动处理,或者您可以自己覆盖并关闭服务。 (2认同)