Keep Dotnet Core Grpc Server running as a console application? Keep Dotnet Core Grpc Server running as a console application? docker docker

Keep Dotnet Core Grpc Server running as a console application?


You can now use Microsoft.Extensions.Hosting pacakge which is a hosting and startup infrastructures for both asp.net core and console application.

Like asp.net core, you can use the HostBuilder API to start building gRPC host and setting it up. The following code is to get a console application that keeps running until it is stopped (for example using 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();    }}

In order to run the gRPC service, you need to start/stop Grpc.Core.Server in a hosted service. A hosted service is basically a piece of code that is run by the host when the host itself is started and the same for when it is stopped. This is represented in the IHostedService interface. That's to say, implement a GrpcHostedService to override the interface:

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

It's simple really. We get an GrpcHostedService instance injected through dependency injection and run StartAsync on it when host is started. When the host is stopped we run StopAsync so that we can gracefully shut everything down including Grpc server.

Then go back to the Program.cs and make some changes:

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

By doing this, the generic host will automatically run StartAsync on our hosted service, which in turn will call StartAsync on the Server instance, essentially start the gRPC server.

When we shut down the host with Control-C, the generic host will automatically call StopAsync on our hosted service, which again will call StopAsync on the Server instance which will do some clean up.

For other configuration in HostBuilder, you can see this blog.


Use ManualResetEvent to block the main thread until you receive a shutdown event.

For example in a trivial situation:

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

For example, in your case, I imagine something like:

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