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の回避策を具体的に見つけようとすることができましたが、それでもこれは正しくないと感じています。サービスを実行し続けるための優れた「本番環境対応」の方法を知っている人はいますか?
これで、asp.netコアとコンソールアプリケーションの両方のホスティングおよびスタートアップインフラストラクチャであるMicrosoft.Extensions.Hosting
pacakgeを使用できます。
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 。
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();
}
}
}