You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

重复调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 20:15:19