重复调用gRPC接口无法向队列添加新对象,计数器不递增问题
gRPC队列与计数器不递增问题排查
问题场景
实现了一个简单的gRPC服务,支持客户端提交任务到队列。测试时客户端循环发送10次请求,服务端日志显示C_SendTask方法被调用10次,但队列长度和计数器始终停留在1,怀疑是否误解了gRPC工作机制。
客户端代码
public async Task SendTaskToSchedulerAsync() { using var channel = GrpcChannel.ForAddress(ClientData.schedulerAddress, new GrpcChannelOptions { HttpHandler = _handler }); var client = new Scheduler.SchedulerClient(channel); var task = new gRPCreq_Task { Type = TaskType.TtGeneratePassword, NLoops = 30, WorkTime = 20 }; try { for(int i = 0; i < 10; i++) { var response = await client.C_SendTaskAsync(task); Console.WriteLine($"Server responded with: {response.TaskStatus}"); } } catch (Exception ex) { Console.WriteLine($"Error communicating with the server: {ex.Message}"); } }
服务端代码
public override async Task<gRPCres_TaskStatus> C_SendTask(gRPCreq_Task request, ServerCallContext context) { _tasks.Enqueue(request); counter++; Console.WriteLine($"Queue size: {_tasks.Count}"); Console.WriteLine($"Counter {counter}"); return await Task.FromResult(new gRPCres_TaskStatus { TaskStatus = TaskStatus.TsTaskAccepted }); }
服务端输出
Queue size: 1 Counter 1 Queue size: 1 Counter 1 Queue size: 1 Counter 1 Queue size: 1 Counter 1 Queue size: 1 Counter 1 Queue size: 1 Counter 1 Queue size: 1 Counter 1 Queue size: 1 Counter 1 Queue size: 1 Counter 1 Queue size: 1 Counter 1
问题原因
不是gRPC工作机制的问题,核心在于gRPC .NET默认的服务实例生命周期配置:
gRPC .NET默认采用InstancePerCall模式,即每个请求到达时会创建一个全新的服务实现类实例。你定义的_tasks队列和counter计数器是类的实例成员,每个请求都使用自己的实例,因此每次调用都是对新实例的成员进行操作,自然不会累加,始终显示为1。
解决方案
方案1:修改服务生命周期为Singleton
在服务注册时,将服务实例的生命周期改为单例,确保所有请求共用同一个服务实例:
// 在Program.cs的服务注册代码中 builder.Services.AddGrpc().AddSingleton<SchedulerService>();
注意:单例模式下,服务类的成员会被多线程同时访问,必须保证线程安全。
方案2:使用静态成员+线程安全组件
将_tasks和counter改为静态成员,同时替换为线程安全的类型:
// 替换为线程安全的队列和原子操作计数器 private static readonly ConcurrentQueue<gRPCreq_Task> _tasks = new ConcurrentQueue<gRPCreq_Task>(); private static int counter = 0; public override async Task<gRPCres_TaskStatus> C_SendTask(gRPCreq_Task request, ServerCallContext context) { _tasks.Enqueue(request); // 使用原子操作更新计数器,避免多线程竞争 Interlocked.Increment(ref counter); Console.WriteLine($"Queue size: {_tasks.Count}"); Console.WriteLine($"Counter {counter}"); return await Task.FromResult(new gRPCres_TaskStatus { TaskStatus = TaskStatus.TsTaskAccepted }); }
推荐使用
ConcurrentQueue而非普通Queue,因为普通队列不是线程安全的,多线程并发操作会导致数据异常;Interlocked.Increment则保证计数器的原子更新,避免竞争问题。
内容的提问来源于stack exchange,提问作者melancholy
相关产品推荐
相关产品推荐

