c# 在 C# 中如何模拟网络延迟和不稳定的并发环境

13次阅读

Task.Delay 是最轻量可控的网络延迟模拟方式,支持固定/随机延迟且不阻塞线程;SemaphoreSlim 可精确限流暴露排队与拒绝问题;httpClient 需配置短超时、禁用连接复用以强化异常场景;三者组合使用需注意顺序、生命周期与复用管理。

c# 在 C# 中如何模拟网络延迟和不稳定的并发环境

Task.Delay 模拟可控网络延迟

真实请求延迟无法在本地复现,但 Task.Delay 是最轻量、最可控的模拟方式。它不阻塞线程,适合高并发场景下的延迟注入。

  • 直接替换 HTTP 调用:把 await httpClient.GetAsync(...) 换成 await Task.Delay(200),就能模拟 200ms 延迟
  • 支持随机延迟:
    await Task.Delay(Random.Shared.Next(100, 800));

    模拟 100–800ms 的抖动

  • 注意不要在同步方法里用 Thread.Sleep,会吃光线程池资源,尤其在 ASP.net Core 中极易触发 ThreadPool starvation

SemaphoreSlim 限制并发数,制造资源争抢

真实服务常因连接池/线程数/限流策略导致请求排队或失败。SemaphoreSlim 可精确控制同时发起的请求数,暴露超时、排队、拒绝等典型问题。

  • 初始化一个 3 并发的信号量:
    private Static readonly SemaphoreSlim _throttle = new(3);
  • 每个请求前加锁:
    await _throttle.WaitAsync(TimeSpan.FromSeconds(2));

    —— 等待超时会抛 OperationCanceledException

  • 务必在 finally 中释放:
    try { /* 请求逻辑 */ } finally { _throttle.Release(); }
  • 不释放会导致后续所有请求永久卡住,这是最常被忽略的坑

HttpClient 配置制造连接异常和重试压力

默认 HttpClient 对连接失败、dns 解析失败、TLS 握手超时等处理过于“温柔”,需主动削弱容错能力来暴露问题。

  • 缩短连接超时:
    var handler = new SocketsHttpHandler { ConnectTimeout = TimeSpan.FromMilliseconds(300) };
  • 禁用连接复用(强制每次新建 TCP 连接):
    handler.PooledConnectionLifetime = TimeSpan.Zero;
  • 配合 HttpRequestExceptionStatusInnerException 类型做差异化重试逻辑,比如对 SocketException 重试,对 HttpRequestExceptionStatus == NULL 判定为连接层失败

组合使用时要注意执行顺序和生命周期

延迟、限流、异常三者叠加后行为不可直觉预测。例如:先限流再延迟,还是先延迟再限流?SemaphoreSlim 实例是否跨测试用例复用?这些细节决定你能不能稳定复现“偶发超时”或“雪崩式失败”。

  • 推荐结构:先 WaitAsync → 再 Task.Delay(模拟请求发送前的排队+网络传输)→ 最后发真实请求
  • HttpClientSemaphoreSlim 应作为 static 或单例管理,否则频繁创建会掩盖连接池问题
  • 单元测试中若用 [Test] 方法逐个跑,记得在 [TearDown] 清空 SemaphoreSlim 当前计数(调用 ReleaseAll()),否则下一个测试可能直接卡死

真实不稳定环境的核心不是“随机”,而是“可复现的组合条件”。把延迟、并发、异常三者当成开关,一个个打开关掉,比写一 Random.Next() 更容易定位下游服务的脆弱点。

text=ZqhQzanResources