使用HttpListener.GetContextAsync异步并行处理请求遇阻塞问题
场景与代码实现
我尝试使用async-await技术让HttpListener并行接收多个请求,这里的“并行”仅针对等待阶段而非处理阶段,符合我的需求。该Listener由BackgroundService类管理,核心监听循环简化后如下:
protected override async Task ExecuteAsync(CancellationToken stoppingToken) { ... while (true) { var context = await listener.GetContextAsync(); var processTask = ProcessRequestAsync(context); } ... }
处理请求的函数简化后:
private async Task ProcessRequestAsync(HttpListenerContext context) { await Task.Delay(5000); context.Response.StatusCode = (int)HttpStatusCode.BadRequest; context.Response.Close(); }
预期与实际行为的差异
按我的理解,循环中第一行会阻塞直到有请求到来,第二行不会阻塞;处理函数执行到await处后,循环会继续调用GetContextAsync()。我预期如果第二个请求在第一个请求的5秒等待期内到达,会立即进入自身的5秒等待阶段,若两个请求间隔1秒,总处理耗时约6秒。
但实际情况是:第二个GetContextAsync()的调用虽及时触发,但await会阻塞到第一个请求的HttpListenerContext响应关闭后才返回,导致两个请求串行处理,总耗时10秒。
另一个测试验证了此行为:将await Task.Delay(5000)移至context.Response.Close()下方后,第二个await GetContextAsync()不再阻塞,请求到达即返回。这表明阻塞与任务无关(两种情况任务总时长相同),而是与HttpListenerContext直接相关。
已尝试的方案与补充信息
- 有人建议使用
.configureAwait(false),我尝试后没有任何变化 - 我记录了处理函数使用的线程,两个请求的处理线程相同,但不确定这是问题原因还是串行调度导致的结果
更新测试结果:疑似Listener类的硬性限制
我做了更多测试,情况更明确:将等待时间拆分为响应关闭前5秒、关闭后5秒,结果两个“并行”请求仅重叠5秒,即请求#2只能在请求#1的响应关闭后开始处理。
以下所有实现方式都得到了相同的结果:
- 上述async-await代码(包括使用
Task.Run()优化的版本) - 旧版异步处理方式(BeginGetContext - EndGetContext)
- 基于Thread的实现(每个Context在不同线程处理)
- 将一个古老的UI相关讨论中的代码示例转为控制台应用测试
所有测试均在极简控制台应用中进行,说明原程序作为BackgroundService的一部分与问题无关。
我尝试了两种简单(未必适用于生产环境)的“解锁”Context#1的方法,但均失败:
- 关闭
HttpListener.Request.InputStream→ 无任何变化 - 用多个Listener处理请求 → 无法在同一端口启动监听(但我怀疑这种方式能绕开原问题)
我曾通过显式线程管理解决该问题,但特别想知道能否用async-await实现此需求,目前未找到该行为的相关官方文档。
内容的提问来源于stack exchange,提问作者avat

