C#控制器中如何等待对象加入列表以返回API响应
问题描述
我现有一个Web API系统,需要向远程设备发送指令,等待其响应后返回给请求方。当前逻辑为生成UUID,远程响应匹配UUID后加入响应列表。请问能否await列表包含指定UUID?若不可行,如何跨类和线程将该对象传递至控制器以返回响应?
现有Web请求代码
[HttpGet] [Route("Areas")] public async Task<IHttpActionResult> GetArea(GetAreas getAreas) { OutScaleManager.LogRequest("Request to getAreas with ip: " + getAreas.IP + " i " + getAreas.startArea + " i " + getAreas.endArea); foreach (NoxApiService noxService in OutScaleManager.nUtil.connectedInstance) { if (noxService.param.IPAddress == getAreas.IP) { noxService.GetAreas(getAreas.startArea, getAreas.endArea); OutScaleManager.LogRequest("getAreas Sent, now moving to queue checks"); string UUID = OutScaleManager.nRAR.NoxRequestHandler(getAreas, RequestType.GETAREAS); if (OutScaleManager.nRAR.GetAreasResponseSend) { await OutScaleManager.nRAR.ResponseList.ContainsKey(UUID); } //if (OutScaleManager.nRAR.GetAreasResponseSend) { return Ok(OutScaleManager.nRAR.NoxRequestHandler(getAreas, RequestType.GETAREAS)); } } } OutScaleManager.LogRequest("This request was not sent, please create an instance and connected through the propper channels."); return Ok("This request was not sent, please create an instance and connected through the propper channels."); }
解决方案
1. 不能直接await ContainsKey
ResponseList.ContainsKey(UUID)是同步方法,返回bool类型,并非可等待的Task,所以你写的await OutScaleManager.nRAR.ResponseList.ContainsKey(UUID)会直接编译报错,这种写法完全不可行。
2. 正确异步等待实现:使用TaskCompletionSource
要实现等待远程响应的异步逻辑,最可靠的方式是用TaskCompletionSource<T>——它能创建一个可等待的Task,当远程响应返回时,完成这个Task,控制器就能拿到结果返回给请求方。
具体实现步骤
- 维护请求跟踪容器:在
NoxRequestResponseHandler(即代码中的nRAR)中,用Dictionary<string, TaskCompletionSource<你的响应类型>>存储每个UUID对应的等待任务源,替代原有的ResponseList。 - 发送请求时创建任务源:生成UUID后,创建
TaskCompletionSource并存入字典,将其Task返回给控制器进行await。 - 响应到达时完成任务:当远程设备返回匹配UUID的响应时,从字典中取出对应的
TaskCompletionSource,调用SetResult传入响应数据,控制器的await会自动完成并返回结果。
修改后核心代码示例
改造NoxRequestResponseHandler类
public class NoxRequestResponseHandler { // 用线程安全字典存储待处理请求的任务源 private readonly ConcurrentDictionary<string, TaskCompletionSource<GetAreasResponse>> _pendingRequests = new ConcurrentDictionary<string, TaskCompletionSource<GetAreasResponse>>(); // 注册请求,返回可等待的Task和对应的UUID public (string Uuid, Task<GetAreasResponse> ResponseTask) RegisterGetAreasRequest(GetAreas request) { string uuid = Guid.NewGuid().ToString(); var tcs = new TaskCompletionSource<GetAreasResponse>(); _pendingRequests.TryAdd(uuid, tcs); return (uuid, tcs.Task); } // 远程响应到达时调用此方法完成任务 public void CompleteGetAreasRequest(string uuid, GetAreasResponse response) { if (_pendingRequests.TryRemove(uuid, out var tcs)) { tcs.SetResult(response); } } // 超时处理:避免请求无限挂起 public void CancelRequest(string uuid) { if (_pendingRequests.TryRemove(uuid, out var tcs)) { tcs.SetCanceled(); } } }
修改控制器方法
[HttpGet] [Route("Areas")] public async Task<IHttpActionResult> GetArea(GetAreas getAreas) { OutScaleManager.LogRequest("Request to getAreas with ip: " + getAreas.IP + " i " + getAreas.startArea + " i " + getAreas.endArea); foreach (NoxApiService noxService in OutScaleManager.nUtil.connectedInstance) { if (noxService.param.IPAddress == getAreas.IP) { // 注册请求,获取UUID和可等待的Task var (uuid, responseTask) = OutScaleManager.nRAR.RegisterGetAreasRequest(getAreas); // 发送指令给远程设备(需携带UUID,让响应带回匹配) noxService.GetAreas(uuid, getAreas.startArea, getAreas.endArea); OutScaleManager.LogRequest("getAreas Sent, waiting for response"); // 等待响应,同时添加超时处理(比如30秒) var completedTask = await Task.WhenAny(responseTask, Task.Delay(30000)); if (completedTask == responseTask) { // 拿到响应返回给请求方 var response = await responseTask; return Ok(response); } else { // 超时取消请求并返回提示 OutScaleManager.nRAR.CancelRequest(uuid); return StatusCode(HttpStatusCode.RequestTimeout, "Request timed out waiting for device response"); } } } OutScaleManager.LogRequest("This request was not sent, please create an instance and connected through the proper channels."); return Ok("This request was not sent, please create an instance and connected through the proper channels."); }
关键注意事项
- 线程安全:使用
ConcurrentDictionary替代普通Dictionary,避免多线程访问时的冲突问题。 - 超时机制:必须添加超时处理,防止请求因设备无响应而长时间挂起,占用服务器资源。
- 资源清理:响应完成或超时后,从字典中移除对应的
TaskCompletionSource,避免内存泄漏。
内容的提问来源于stack exchange,提问作者xguysOG
相关产品推荐
相关产品推荐

