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

SemaphoreSlim未按预期工作:多线程并发仍生成多个Token

问题分析与解决方法

你的核心问题在于SemaphoreSlim未实现全局同步,同时缺少内存级的Token缓存机制,导致并发请求绕过同步逻辑重复生成Token。以下是具体原因和修复方案:

核心问题点

  1. SemaphoreSlim作用域错误:如果Services类在ASP.NET Core中注册为Scoped(默认行为),每个请求会创建一个独立的Services实例,每个实例的_semaphore都是局部的,无法跨请求同步,多个请求可以同时进入Token生成逻辑。
  2. 无内存缓存校验:仅依赖数据库查询判断Token有效性,并发请求可能同时查询到"无有效Token"的结果,进而触发多次Token生成。

修复方案

1. 全局化SemaphoreSlim

将_semaphore改为静态字段,确保所有Services实例共用同一个信号量,实现全局同步:

public class Services
{
    // ... 其他字段不变
    // 改为静态字段,全局唯一
    private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
    // 新增内存缓存Token和过期时间
    private static string _currentToken;
    private static DateTime _tokenExpiry;

    // ... 构造函数不变
}

2. 优化Token获取逻辑,增加内存缓存校验

在GetAccessTokenAsync中,先快速检查内存缓存(无需等待锁),进入锁后再次校验(避免等待期间Token已被更新),最后再查数据库或生成新Token:

private async Task<string> GetAccessTokenAsync()
{
    // 第一步:快速检查内存缓存,减少锁竞争
    if (!string.IsNullOrEmpty(_currentToken) && DateTime.UtcNow < _tokenExpiry)
    {
        return _currentToken;
    }

    await _semaphore.WaitAsync();
    try
    {
        // 第二步:进入锁后再次检查,防止等待期间Token已被其他线程更新
        if (!string.IsNullOrEmpty(_currentToken) && DateTime.UtcNow < _tokenExpiry)
        {
            return _currentToken;
        }

        // 第三步:检查数据库中的有效Token
        var dbValidToken = await GetValidTokenFromDatabaseAsync();
        if (dbValidToken != null && DateTime.UtcNow < dbValidToken.ExpiryTime.AddMinutes(-1))
        {
            // 更新内存缓存,提前1分钟过期避免临界时间点问题
            _currentToken = dbValidToken.Token;
            _tokenExpiry = dbValidToken.ExpiryTime.AddMinutes(-1);
            return _currentToken;
        }

        // 第四步:生成新Token并更新缓存和数据库
        var newToken = await RequestAccessTokenAsync();
        await SaveNewTokenToDatabaseAsync(newToken);
        
        _currentToken = newToken.AccessToken;
        // 提前1分钟设置过期时间,预留缓冲
        _tokenExpiry = newToken.ExpiryTime.AddMinutes(-1);
        return _currentToken;
    }
    finally
    {
        _semaphore.Release();
    }
}

// 补充数据库操作的占位方法
private async Task<DbTokenModel> GetValidTokenFromDatabaseAsync()
{
    // 实现查询数据库中未过期Token的逻辑
    return null;
}

private async Task SaveNewTokenToDatabaseAsync(TokenResponse newToken)
{
    // 实现将新Token保存到数据库的逻辑,可同时清理过期Token
}

3. 调整Services的注册生命周期

在ASP.NET Core的Program.cs中,将Services注册为Singleton,减少实例创建开销并确保逻辑一致性:

builder.Services.AddSingleton<Services>();

关键优化说明

  • 双重校验锁:内存缓存的两次检查,既减少了锁竞争,又避免了等待锁期间Token已被更新的问题。
  • 过期缓冲:Token过期时间提前1分钟设置,避免刚好在Token过期瞬间的并发请求重复生成Token。
  • 全局信号量:静态SemaphoreSlim确保同一时间只有一个线程能进入Token生成逻辑。

内容的提问来源于stack exchange,提问作者StealthGhost

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 03:15:38