为什么Task.Run(async () => await StringGetAsync()).Wait() 会减少StackExchange.Redis的超时异常?

人工智能 2026-07-07

在负载测试期间,我在从Redis读取一个纪元值(一个简单的整数)时遇到了大量的Redis超时异常。该应用是一个同步实现的.NET Core Web API项目,值的检索使用StackExchange.Redis的 database.StringGet()。为了模拟其他工作,我在代码中加入了一个2 秒的Thread.Sleep()。

该负载测试在我的本地开发环境中大致模拟了5 万次请求。在这样的负载下,我遇到了大量Redis超时。我的初始假设是,这种行为是可以预期的,很可能是由线程池枯竭引起的,因为同步的Redis调用会阻塞请求线程直到I/O完成。

我理解将请求流程完全改为异步执行——从控制器层到Redis访问层——很可能会降低或消除这些超时问题。

然而,出于实验目的,我并未将控制器改为异步,而是将同步的Redis调用替换为 database.StringGetAsync(),并将其包裹在 Task.Run(...).Wait() 中。我还在 Task.Run() 内部引入了一个人工的 Task.Delay(),以模拟额外的繁重处理。此前的Thread.Sleep() 已被移除。

概念上,我预计这两种方法(StringGet()Task.Run(...).Wait())的行为应该类似,因为它们最终都会阻塞执行,且仍然会导致线程枯竭和Redis超时。

令人惊讶的是,在相同负载条件下,Task.Run(...).Wait() 的实现并未出现Redis超时。

我进行了广泛的调查,并使用了多种AI工具,但我无法找到一个令人信服的解释,解释为何将异步的Redis操作包裹在 Task.Run().Wait() 内部的行为,在高负载下与直接同步的 StringGet() 调用表现不同。

public string GetEpochSync(string key)
{
    var value = _database.StringGet(key);
    Thread.Sleep(2000);
    return value;
}

// Async version
public async Task<string> GetEpochAsync(string key)
{
    var value = await _database.StringGetAsync(key);
    await Task.Delay(2000);
    return value;
}

// Sync wrapper with toggle
public string GetEpoch(string key, bool useAsync)
{
    if (useAsync)
    {
        // Run async inside sync
        return Task.Run(async () =>
        {
            return await GetEpochAsync(key);
        }).GetAwaiter().GetResult();
    }

    return GetEpochSync(key);
}

解决方案

问题在于你的线程本来就是一个异步任务。 (我只是从字里行间推断出来的;你是在一个Web服务方法中尝试的。别自责。我不久前也犯过同样的错。)

如果你处于接近最大线程池负载的状态,并且在其中尝试调用Thread.Sleep(),线程池可能会卡住,且需要很长时间来创建更多线程以扩充线程池。

如果你必须这样做,如我一样,需要考虑以更高的初始线程数来创建线程池。你可以在Program.cs中可靠地进行调整。

Task.Run() 看起来能缓解问题的原因是,调用Task.Run() 让线程池更早地注意到你正在把它塞满。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章