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

IDistributedCache.Get 和 IDistributedCache.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 负载、序列化开销——这些对 Get 和 GetAsync 是一样的。
真正差异在于调度行为:
-
Get:占用一个线程池线程全程等待,期间无法响应其他请求 -
GetAsync:发起 I/O 后立即释放线程,操作系统完成网络读取后唤醒回调,线程复用率更高
在 .NET 6+ 默认配置下,IDistributedCache 的 Redis 实现(StackExchange.Redis)完全基于异步 I/O,所以 Get 内部也是靠 GetAsync + Wait() 模拟出来的——这多一次线程挂起/唤醒开销,纯属负优化。
除非你在写控制台工具或单元测试这种对吞吐无要求的场景,否则永远优先选 GetAsync,并确保调用链上所有方法都标记 async、用 await。漏掉一个 await,就等于白加。










