gRPC服务端Task参数作用域问题:如何在外部及主线程使用请求参数
解决gRPC服务端请求参数跨实例/线程访问问题
你遇到的核心问题有两个:
- 实例不共享:
StartServer方法里创建了新的LoggingImpl实例绑定到gRPC服务,而你外部调用GetRequestInfo的是另一个实例,两个实例的currentRequest完全独立,所以外部拿不到值。 - 线程安全风险:gRPC服务方法是并发执行的,多个客户端请求会同时修改
currentRequest,直接赋值会导致数据覆盖或线程安全问题。
1. 修复实例不一致问题
修改StartServer方法,用当前实例绑定服务,而非创建新实例:
public void StartServer() { server = new Server { Services = { Logging.BindService(this) }, // 使用当前实例,而非new LoggingImpl() Ports = { new ServerPort("127.0.0.1", 50010, ServerCredentials.Insecure) } }; server.Start(); }
2. 线程安全的请求存储方案
根据业务需求选择合适的存储方式:
方案A:保存最新请求
用原子操作确保多线程下赋值/读取的安全性:
private RequestInfo _currentRequest; public override Task<LoggingResponse> LoggingMCM(RequestInfo request, ServerCallContext context) { // 原子操作更新最新请求 Interlocked.Exchange(ref _currentRequest, request); Console.WriteLine(_currentRequest); return Task.FromResult(new LoggingResponse { Result = "this is a reply from Unity Server" }); } public void GetRequestInfo() { // 原子操作安全读取 var request = Interlocked.CompareExchange(ref _currentRequest, null, null); Console.WriteLine(request); }
方案B:保存所有请求
用线程安全集合存储所有请求记录:
private readonly ConcurrentQueue<RequestInfo> _allRequests = new ConcurrentQueue<RequestInfo>(); public override Task<LoggingResponse> LoggingMCM(RequestInfo request, ServerCallContext context) { _allRequests.Enqueue(request); Console.WriteLine(request); return Task.FromResult(new LoggingResponse { Result = "this is a reply from Unity Server" }); } // 示例:获取所有请求 public List<RequestInfo> GetAllRequests() { return _allRequests.ToList(); }
3. 在其他类/主线程中访问请求
跨类访问
确保其他类持有同一个LoggingImpl实例的引用,直接调用对应的方法或访问属性:
// 比如在ClientController中注入LoggingImpl实例 public class ClientController { private readonly LoggingImpl _loggingServer; public ClientController(LoggingImpl loggingServer) { _loggingServer = loggingServer; } public void ShowLatestRequest() { _loggingServer.GetRequestInfo(); } }
Unity主线程处理
如果是Unity环境,gRPC服务方法运行在后台线程,需要回到主线程处理请求数据,可以实现一个简易主线程调度器:
// 简易Unity主线程调度器 public static class MainThreadDispatcher { private static readonly Queue<Action> _actions = new Queue<Action>(); public static void Enqueue(Action action) { lock (_actions) { _actions.Enqueue(action); } } // 在Unity的Update方法中调用此方法 public static void Update() { lock (_actions) { while (_actions.Count > 0) { _actions.Dequeue().Invoke(); } } } }
然后在gRPC方法中使用:
public override Task<LoggingResponse> LoggingMCM(RequestInfo request, ServerCallContext context) { // 拷贝请求数据,避免后续被并发修改 var requestCopy = new RequestInfo { // 复制所有需要的字段 Id = request.Id, Message = request.Message }; // 将处理逻辑放到主线程执行 MainThreadDispatcher.Enqueue(() => { Console.WriteLine(requestCopy); // 通知ClientController处理请求 controller.OnNewRequestReceived(requestCopy); }); return Task.FromResult(new LoggingResponse { Result = "this is a reply from Unity Server" }); }
内容的提问来源于stack exchange,提问作者BenJ
相关产品推荐
相关产品推荐

