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

时间:2017-08-31 19:55:59

标签: c# docker .net-core grpc

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

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

var rawurl = "https://emp.com/some/path?key1.name=a%20line%20with%3D&key2=val2&key2=valdouble&key3=&key%204=44#book1";
var uri = new Uri(rawurl);
Dictionary<string, string> queryString = ParseQueryString(uri.Query);

// queryString return:
// key1.name, a line with=
// key2, valdouble
// key3, 
// key 4, 44

public Dictionary<string, string> ParseQueryString(string requestQueryString)
{
    Dictionary<string, string> rc = new Dictionary<string, string>();
    string[] ar1 = requestQueryString.Split(new char[] { '&', '?' });
    foreach (string row in ar1)
    {
        if (string.IsNullOrEmpty(row)) continue;
        int index = row.IndexOf('=');
        if (index < 0) continue;
        rc[Uri.UnescapeDataString(row.Substring(0, index))] = Uri.UnescapeDataString(row.Substring(index + 1)); // use Unescape only parts          
     }
     return rc;
}

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

Console.ReadKey();

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

2 个答案:

答案 0 :(得分:13)

您现在可以使用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,而托管服务又会在Server实例上调用StartAsync,实质上是启动gRPC服务器。

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

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

答案 1 :(得分: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");
  }
}

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

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