ASP.NET Core SignalR中TaskCompletionSource授权码重试逻辑阻塞问题
问题分析与解决方案
核心问题
你的代码无法正常工作的原因有两个:
tcs是Send方法内的局部变量,SendActivationCode属于完全独立的调用上下文,根本无法访问这个变量,自然无法触发SetResult唤醒等待的任务。- SignalR的Hub实例是每次请求都会创建新实例的,哪怕你把
tcs改成类成员变量,Send和SendActivationCode对应的也是不同的Hub实例,依然无法共享这个tcs。
解决方案
需要用一个线程安全的全局存储,将每个客户端连接ID对应的TaskCompletionSource<string>关联起来,这样SendActivationCode就能根据当前连接找到对应的tcs并完成任务。
修正后的服务端代码
using System.Collections.Concurrent; using Microsoft.AspNetCore.SignalR; public class AuthHub : Hub { // 线程安全字典,存储连接ID对应的授权码等待任务 private static readonly ConcurrentDictionary<string, TaskCompletionSource<string>> _authTcsDict = new(); public async Task Send(string json) { try { bool isAuth = false; // 尝试用传入的json中的授权码验证 isAuth = AuthService(json); // 循环请求授权码直到验证通过 while (!isAuth) { var tcs = new TaskCompletionSource<string>(); // 将当前连接的tcs存入字典,避免重复请求 if (!_authTcsDict.TryAdd(Context.ConnectionId, tcs)) { await Clients.Caller.SendAsync("AuthError", "已有授权请求在处理中"); return; } // 向客户端发送授权码请求 await Clients.Caller.SendAsync("RequestActivationCode"); try { // 等待客户端返回授权码 string activationCode = await tcs.Task; // 用新授权码重新验证 isAuth = TryAuthAgainWithNewCode(activationCode); } finally { // 无论成功失败,清理字典中的tcs,避免内存泄漏 _authTcsDict.TryRemove(Context.ConnectionId, out _); } } // 验证通过后的业务逻辑 await Clients.Caller.SendAsync("AuthSuccess", "授权验证通过"); } catch (Exception ex) { await Clients.Caller.SendAsync("AuthError", $"授权过程出错:{ex.Message}"); } } public async Task SendActivationCode(string code) { // 根据当前连接ID获取对应的等待任务 if (_authTcsDict.TryGetValue(Context.ConnectionId, out var tcs)) { try { // 设置结果,唤醒Send方法中的等待 tcs.SetResult(code); } catch (Exception ex) { tcs.SetException(ex); } } else { await Clients.Caller.SendAsync("AuthError", "无正在处理的授权请求"); } } // 模拟初始授权验证逻辑 private bool AuthService(string json) { // 替换为你的实际授权代码 return false; } // 模拟新授权码验证逻辑 private bool TryAuthAgainWithNewCode(string activationCode) { // 替换为你的实际验证逻辑,示例中判断是否为指定码 return activationCode == "32453"; } }
优化后的客户端代码
hubConnection.on("RequestActivationCode", () => { // 实际场景中替换为用户输入授权码的交互 const activationCode = prompt("请输入授权码:"); if (activationCode) { hubConnection.invoke("SendActivationCode", activationCode) .catch(err => console.error("发送授权码失败:", err)); } else { console.error("用户取消输入授权码"); } }); // 监听授权结果通知 hubConnection.on("AuthSuccess", (message) => { console.log(message); }); hubConnection.on("AuthError", (message) => { console.error(message); });
关键注意事项
- 使用
ConcurrentDictionary保证多线程下的线程安全,因为SignalR的Hub方法是并发调用的。 - 在
finally块中移除字典中的tcs,避免长期未清理导致内存泄漏。 - 增加了重复请求的判断,防止同一连接多次触发
Send导致的冲突。 - 补充了错误通知的SignalR方法,让客户端能及时获取授权过程中的异常信息。
内容的提问来源于stack exchange,提问作者Vladimir
相关产品推荐
相关产品推荐

