简体   繁体   中英

An using GRPC Channel concurrently in Net Core 3.0

According the documentation , a gRPC client is created from a channel. Multiple concrete gRPC clients can be created from a channel, including different types of clients, but I didn't find any information about concurrency.

So, my question, Can I use the channel for concurrent calls like below?

var channel = GrpcChannel.ForAddress("https://localhost:5001");

// the first task
Task.Run(() => {
     var client = new Greet.GreeterClient(channel);
     var response = await client.SayHelloAsync(new HelloRequest { Name = "World" });

     Console.WriteLine("Greeting: " + response.Message); 
 });
// the second task
Task.Run(() => {
     var client = new Greet.GreeterClient(channel);
     var response = await client.SayHelloAsync(new HelloRequest { Name = "World" });

     Console.WriteLine("Greeting: " + response.Message); 
 });

Or I need to create own channel for each thread(task).

As @Mike has noted, The GrpcChannel is using HttpClient under the hood. So, according this , we probably can create a single http client and reuse it throughout the life of an application.

HttpClient is intended to be instantiated once and re-used throughout the life of an application.

I have built a simple example to investigate the area.

GRPS service:

public class GreeterService
    : Greeter.GreeterBase
{
    private readonly ILogger<GreeterService> _logger;
    public GreeterService(ILogger<GreeterService> logger)
    {
        _logger = logger;
    }

    //Server side handler of the SayHello RPC
    public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
    {
        _logger.LogInformation($"Sending hello to {request.Name}");
        Thread.Sleep(500);
        return Task.FromResult(new HelloReply { Message = "Hello " + request.Name });
    }
}

The single channel for all threads

        string uri = $"https://localhost:5001";

        var channel = GrpcChannel.ForAddress(uri, new GrpcChannelOptions()
        {
            HttpClient = HttpClientFactory.Create(),
            DisposeHttpClient = true
        });

        int threadCount = 6;
        Thread[] threads = new Thread[threadCount];

        for(int i = 0; i < threadCount; ++i)
        {
            threads[i] = new Thread((index) =>
            {
                Console.WriteLine($"Thread({(int)index}) has been started!");
                for (int req = 0; req < 75; ++req)
                {
                    var client = new Greeter.GreeterClient(channel);
                    client.SayHello(new HelloRequest()
                    {
                        Name = $"Thread {(int)index}"
                    });
                }

                Console.WriteLine($"Thread({(int)index}) has been finished!");
            });
        }

        for (int i = 0; i < threadCount; ++i)
        {
            threads[i].Start(i);
        }

        for (int i = 0; i < threadCount; ++i)
        {
            threads[i].Join();
        }

Own channel for each thread

        string uri = $"https://localhost:5001";

        int threadCount = 6;
        Thread[] threads = new Thread[threadCount];

        for(int i = 0; i < threadCount; ++i)
        {
            threads[i] = new Thread((index) =>
            {
                var channel = GrpcChannel.ForAddress(uri, new GrpcChannelOptions()
                {
                    HttpClient = HttpClientFactory.Create(),
                    DisposeHttpClient = true
                });

                Console.WriteLine($"Thread({(int)index}) has been started!");
                for (int req = 0; req < 75; ++req)
                {
                    var client = new Greeter.GreeterClient(channel);
                    client.SayHello(new HelloRequest()
                    {
                        Name = $"Thread {(int)index}"
                    });
                }

                Console.WriteLine($"Thread({(int)index}) has been finished!");
            });
        }

        Thread.Sleep(1000 * 10);

        for (int i = 0; i < threadCount; ++i)
        {
            threads[i].Start(i);
        }

        for (int i = 0; i < threadCount; ++i)
        {
            threads[i].Join();
        }

Summary, I can make a conclusion that we can create a single GRPC channel and reuse it in the app. But I don't know how effective using one channel for many request is

i see that the question is answered but I wanted to add that there are sections in docs about client concurrency. If someone new read this question i hope this will be helpful.

Microsoft docs

Introduction to gRPC on .NET Core

Chapter " Reuse gRPC channels " on gRPC - Performance and practices .

Channels are safe to share and reuse between gRPC calls:

  • gRPC clients are created with channels. gRPC clients are lightweight objects and don't need to be cached or reused.
  • Multiple gRPC clients can be created from a channel, including different types of clients.
  • A channel and clients created from the channel can safely be used by multiple threads.
  • Clients created from the channel can make multiple simultaneous calls.

" Connection concurrency " chapter on this topic is also very helpful.

NOTE: Look topic Configuration for configuring services and client channels.

gRPC API Documentation

Namespace Grpc.Net.Client

GrpcChannel

Represents a gRPC channel. Channels are an abstraction of long-lived connections to remote servers. Client objects can reuse the same channel. Creating a channel is an expensive operation compared to invoking a remote call so in general you should reuse a single channel for as many calls as possible.

gRPC Documentation

There are no specifics about client concurrency, but Core concepts topic is also very helpful by giving glimpses on how gRPC works underhood.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM