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

ASP.NET Core SignalR中TaskCompletionSource授权码重试逻辑阻塞问题

问题分析与解决方案

核心问题

你的代码无法正常工作的原因有两个:

  1. tcs是Send方法内的局部变量,SendActivationCode属于完全独立的调用上下文,根本无法访问这个变量,自然无法触发SetResult唤醒等待的任务。
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 06:50:11