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

并发请求下调用Azure Blob Storage的GetUserDelegationKey生成SAS Token时触发System.ObjectDisposedException异常求助

解决Azure Blob Storage并发生成User Delegation SAS时的ObjectDisposedException问题

从你的异常栈和代码来看,这个问题的核心是没有正确复用Azure SDK的客户端实例与身份凭据,加上并发场景下频繁创建/销毁资源,导致内部流被提前释放。下面是具体分析和解决方案:

问题根源拆解

  1. 频繁创建客户端与凭据:你的代码每次调用GetUrlWithAccessToken时,都会新建BlobServiceClient和DefaultAzureCredential实例。Azure SDK的客户端(如BlobServiceClient、BlobClient)是线程安全且设计为可复用的,频繁创建会耗尽连接池资源、导致内部HTTP管道被意外释放,并发时就会触发Cannot access a closed Stream异常。
  2. 重复调用GetUserDelegationKey:这个方法需要调用Azure Storage服务端API,频繁调用不仅增加服务端压力,还会在并发场景下引发多个请求同时操作内部HTTP流,导致资源冲突。

具体解决方案

1. 复用Azure SDK客户端与凭据

将BlobServiceClient和DefaultAzureCredential作为服务类的私有成员,在构造函数中仅初始化一次,而非每次方法调用都新建。

2. 缓存User Delegation Key

User Delegation Key的有效期最长可达7天,完全可以缓存一段时间(比如1小时),避免每次请求都调用服务端API。同时用锁确保并发下只有一个线程刷新密钥,避免重复请求。

修改后的代码示例

// 在你的DefsultCredntialStorageService类中添加私有成员
private readonly BlobServiceClient _blobServiceClient;
private readonly DefaultAzureCredential _defaultCredential;
private readonly SemaphoreSlim _keyRefreshLock = new SemaphoreSlim(1, 1);
private UserDelegationKey _cachedUserDelegationKey;
private DateTimeOffset _cachedKeyExpiry;
private readonly AmsSettings _amsSettings;

// 构造函数中初始化复用的客户端和凭据
public DefsultCredntialStorageService(AmsSettings amsSettings)
{
    _amsSettings = amsSettings;
    _defaultCredential = GetDefaultCredentials(); // 只初始化一次
    _blobServiceClient = GetBlobServiceClient(_amsSettings.StorageEndPointUrl); // 复用BlobServiceClient
}

public async Task<string> GetUrlWithAccessToken(string url, double expiredInHours = 0)
{
    if (string.IsNullOrEmpty(url)) return null;
    
    var uri = new Uri(url);
    // 复用已有的凭据创建BlobClient(BlobClient轻量,直接创建影响不大)
    var blobClient = new BlobClient(uri, _defaultCredential);

    // 获取或刷新缓存的User Delegation Key
    await _keyRefreshLock.WaitAsync();
    try
    {
        // 提前5分钟刷新密钥,避免密钥过期导致请求失败
        if (_cachedUserDelegationKey == null || DateTimeOffset.UtcNow >= _cachedKeyExpiry.AddMinutes(-5))
        {
            _cachedKeyExpiry = DateTimeOffset.UtcNow.AddHours(1);
            // 使用异步方法避免阻塞线程,提升并发性能
            _cachedUserDelegationKey = await _blobServiceClient.GetUserDelegationKeyAsync(DateTimeOffset.UtcNow, _cachedKeyExpiry);
        }
    }
    finally
    {
        _keyRefreshLock.Release();
    }

    return GetBlobUrlWithAccessToken(_cachedUserDelegationKey, blobClient, url, expiredInHours);
}

// 保留原有的GetBlobUrlWithAccessToken方法不变
private string GetBlobUrlWithAccessToken(UserDelegationKey userDelegationKey, BlobClient blobClient, string url, double expiredInHours)
{
    BlobSasBuilder sasBuilder = new BlobSasBuilder()
    {
        BlobContainerName = blobClient.GetParentBlobContainerClient().Name,
        BlobName = blobClient.Name,
        Resource = "b"
    };
    sasBuilder.ExpiresOn = DateTimeOffset.UtcNow.AddHours(expiredInHours);
    sasBuilder.SetPermissions(BlobSasPermissions.Read);

    BlobUriBuilder blobUriBuilder = new BlobUriBuilder(blobClient.Uri)
    {
        Sas = sasBuilder.ToSasQueryParameters(userDelegationKey, blobClient.AccountName)
    };
    var sasToken = blobUriBuilder.Sas.ToString();
    return url + "?" + sasToken;
}

额外优化建议

  • 升级Azure SDK版本:确保Azure.Storage.Blobs NuGet包是最新稳定版,旧版本可能存在并发场景下的资源泄漏bug。
  • 避免手动管理流资源:不要在代码中手动释放Azure SDK内部的流对象,SDK会自动处理资源生命周期。
  • 监控服务端限流:负载测试时注意观察Azure Storage的监控指标,如果出现429限流响应,可能需要调整请求速率或申请提高配额,但你的异常更偏向客户端资源问题,优先处理上述方案。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.01 02:02:43