c# IDistributedCache 的 Get 和 GetAsync 有什么区别

1次阅读

idistributedcache.get 是同步阻塞调用,会占用线程池线程直至完成;getasync 是异步非阻塞,返回 task,支持取消和高并发场景,web 应用中必须优先使用 getasync 并正确 await。

c# IDistributedCache 的 Get 和 GetAsync 有什么区别

IDistributedCache.GetIDistributedCache.GetAsync 的根本区别是同步阻塞 vs 异步非阻塞

前者会**直接阻塞当前线程**直到缓存读取完成(比如 redis 网络往返结束),后者返回 Task<byte></byte>,让线程可以去做别的事,等 I/O 完成后再继续——这对 ASP.NET Core 这类高并发服务至关重要。

在 Web 应用中,用 .Get() 而不是 .GetAsync() 会导致线程池饥饿、吞吐量下降,尤其在缓存后端(如 Redis)延迟升高时更明显。这不是“能不能用”的问题,而是“该不该用”的架构选择。

为什么 Get 还存在?它适合什么场景

IDistributedCache.Get 是同步包装,底层通常调用 GetAsync().GetAwaiter().GetResult()(或类似实现)。它只在极少数明确需要同步上下文的场合才合理:

  • 单元测试中快速验证逻辑,不希望引入 async Task 测试方法
  • Legacy 同步代码迁移过渡期,且确认调用不会进入 Web 请求生命周期
  • 后台定时任务里用到了非 async 主循环(但更推荐统一改用 GetAsync + await

注意:.Get() 在 ASP.NET Core 请求处理中调用,可能引发死锁或 AspNetCoreSynchronizationContext 相关异常,尤其是配合某些旧版中间件时。

GetAsync 的参数和典型用法细节

GetAsync 签名是:

public Task<byte[]?> GetAsync(string key, CancellationToken token = default);

关键点:

  • key 必须是非 NULL 字符串;传 null 会直接抛 ArgumentNullException
  • CancellationToken 可用于主动取消等待(例如请求超时、客户端断开),不传则用 default(即无取消信号)
  • 返回值是 byte[]?:缓存未命中时为 null,不是空数组;需判空,不能直接 Encoding.UTF8.GetString(result)
  • 它只负责读原始字节,不自动反序列化;若你存的是 json 字符串,读出来后得自己 JsonSerializer.Deserialize<t>(bytes)</t>

常见误写示例(错在没 await):

var data = _cache.GetAsync("user:123"); // ❌ 返回 Task,没 await,data 是 Task 对象本身 string json = Encoding.UTF8.GetString(data.Result); // ❌ 阻塞式取 Result,等同于 Get()

正确写法:

var bytes = await _cache.GetAsync("user:123"); if (bytes is not null) {     string json = Encoding.UTF8.GetString(bytes);     // 处理 json... }

性能与兼容性影响:别被名字骗了

名字带 “Async” 不代表一定快,但代表「不抢线程」。实际耗时取决于网络 RTT、Redis 负载、序列化开销——这些对 GetGetAsync 是一样的。

真正差异在于调度行为:

  • Get:占用一个线程池线程全程等待,期间无法响应其他请求
  • GetAsync:发起 I/O 后立即释放线程,操作系统完成网络读取后唤醒回调,线程复用率更高

在 .NET 6+ 默认配置下,IDistributedCache 的 Redis 实现(StackExchange.Redis)完全基于异步 I/O,所以 Get 内部也是靠 GetAsync + Wait() 模拟出来的——这多一次线程挂起/唤醒开销,纯属负优化。

除非你在写控制台工具或单元测试这种对吞吐无要求的场景,否则永远优先选 GetAsync,并确保调用链上所有方法都标记 async、用 await。漏掉一个 await,就等于白加。

text=ZqhQzanResources