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

使用TestServer+Task.WhenAll并发请求时触发System.ObjectDisposedException(HttpContext已释放)的问题排查与解决求助

TestServer+Task.WhenAll并发请求时触发System.ObjectDisposedException(HttpContext已释放)的问题排查与解决求助

各位大佬好,我最近在做一个并发测试场景,想模拟多个用户同时通过自定义中间件发起请求。原本计划用TestServer,把每个请求包装成独立的Task,再通过Task.WhenAll实现并发,大部分情况都能正常跑,但碰到了一个棘手的问题——当中间件里调用context.AuthenticateAsync和context.SignInAsync时,会抛出如下异常:

fail: [namespace].[mycustommiddleware][0] Request has finished and HttpContext disposed. Object name: 'HttpContext'.
System.ObjectDisposedException: Request has finished and HttpContext disposed.
Object name: 'HttpContext'.
at Microsoft.AspNetCore.Http.DefaultHttpContext.ThrowContextDisposed()
at Microsoft.AspNetCore.Http.DefaultHttpContext.get_Features()
at Microsoft.AspNetCore.Authentication.RequestPathBaseCookieBuilder.Build(HttpContext context, DateTimeOffset expiresFrom)
at Microsoft.AspNetCore.Http.CookieBuilder.Build(HttpContext context)
at Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler.BuildCookieOptions()
at Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler.HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
at Microsoft.AspNetCore.Authentication.AuthenticationService.SignInAsync(HttpContext context, String scheme, ClaimsPrincipal principal, AuthenticationProperties properties)

以下是我设置任务和创建请求的测试代码:

[Test]
public async Task KeepMvcSessionAliveMiddleware_ShouldNotMixTokens()
{
    // Arrange
    var baseUrl = $"http://localhost:{5050}";
    var companies = new[] { "ACME", "Globex", "Initech", "Umbrella", "WayneTech" };
    var usersPerCompany = 3;
    var userPool = companies
        .SelectMany(company => Enumerable.Range(1, usersPerCompany).Select(i => {
            var tokenDescriptor = GetTokenDescriptor(company);
            var refreshTokenDescriptor = GetRefreshTokenDescriptor();
            return new {
                UserName = $"{company}_User{i}",
                Company = company,
                Token = _tokenHandler.CreateToken(tokenDescriptor),
                RefreshToken = _tokenHandler.CreateToken(refreshTokenDescriptor)
            };
        }))
        .ToArray();

    using (var warmupHandler = new HttpClientHandler { CookieContainer = new CookieContainer() })
    using (var warmupClient = new HttpClient(warmupHandler) { BaseAddress = new Uri(baseUrl) })
    {
        var warmupCookie = CreateCookie("WarmUp", "Test", _dataProtectionProvider);
        warmupHandler.CookieContainer = warmupCookie;
        await warmupClient.GetAsync("/warmup");
    }

    // Act
    var tasks = Enumerable.Range(1, 50).Select(async i => {
        var random = new Random();
        var user = userPool[random.Next(userPool.Length)];
        using (var handler = new HttpClientHandler { CookieContainer = CreateCookie(user.Company, user.UserName, _dataProtectionProvider) })
        using (var client = new HttpClient(handler) { BaseAddress = new Uri(baseUrl) })
        {
            handler.CookieContainer.Add(new Cookie("RefreshToken", user.RefreshToken, "/", "localhost"));
            handler.CookieContainer.Add(new Cookie("Token", user.Token, "/", "localhost"));
            var task = client.GetAsync("/");
            _activeRequests.Add(task);
            var response = await task;
            response.EnsureSuccessStatusCode();
            Assert.That(response, Is.Not.Null);
        }
    });

    await Task.WhenAll(tasks.ToArray());

    foreach (var log in _loggerProvider.Logs)
    {
        TestContext.WriteLine(log);
    }
}

我自己琢磨着,会不会是HttpContext被多个客户端共享了?后续请求用到的HttpContext已经被之前的请求释放掉了?但我实在搞不懂用TestServer做并发测试时,这里面的底层逻辑到底哪里理解错了,有没有办法避免HttpContext在请求之间被释放呢?

对了,补充一下中间件的InvokeAsync方法代码:

public async Task InvokeAsync(HttpContext context)
{
    try
    {
        var jwtExp = _configuration.GetValue<int>("JWT:Expiration", 2);
        if (context.Response.HasStarted) return;

        // Check if the user is authenticated
        if (!RequestingStaticFile(context) && context.User.Identity!.IsAuthenticated)
        {
            var model = GetTokenCookies(context);
            if (string.IsNullOrEmpty(model.RefreshToken) || (await IsRefreshTokenExpired(model.RefreshToken)))
            {
                _logger.Log(LogLevel.Information, $"Refresh token expired or missing. Logging out user: {context.User.Identity.Name}");
                // Refresh token has expired. End the session
                await context.SignOutAsync();
                // Call the next middleware in the pipeline (which will be YARP)
                await _next(context);
                return;
            }

            // Check the cache for the session timeout value
            var companyName = context.User.Claims.FirstOrDefault(c => c.Type == "Company")?.Value.Replace(" ", string.Empty) ?? "Company";
            var cacheKey = $"SessionTimeout_{companyName}_{context.User.Identity.Name}";
            // Attempt to retrieve the cached session timeout...(原内容未完整部分)
        }
        // 后续中间件逻辑...
    }
    catch (Exception ex)
    {
        // 异常处理逻辑...
    }
}

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 12:58:08