Blazor WASM中通过HttpClient消费IAsyncEnumerable时取消请求无效
Blazor HttpClient消费IAsyncEnumerable时取消请求不终止服务器任务的解决方法
问题描述
在Blazor页面中通过HttpClient消费服务器端返回的IAsyncEnumerable,数据流可正常接收,但点击按钮取消请求时,服务器端的异步枚举任务不会终止,仍持续生成数据。
关键现象
当服务器端代码仅包含Task.Delay循环(不使用yield return返回枚举项)时,客户端取消请求后,服务器的CancellationToken能正常触发取消;但使用yield return返回IAsyncEnumerable时,取消机制失效。
问题原因
- 客户端取消请求后,未及时终止异步枚举器的迭代,导致HTTP连接未立即关闭,服务器端无法感知客户端断开。
- 原客户端代码手动检查取消令牌后仅执行
break,但DeserializeAsyncEnumerable的枚举器仍在等待数据流,连接保持活跃,服务器的取消令牌不会触发。
解决方案
客户端代码调整
确保取消令牌直接作用于异步枚举过程,通过WithCancellation让枚举器在令牌触发时立即终止,同时正确处理异常并释放资源:
this._cts = new CancellationTokenSource(); using var client = factory.CreateClient("Default"); using var req = new HttpRequestMessage(HttpMethod.Post, uri); req.SetBrowserResponseStreamingEnabled(true); try { var response = await client.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, this._cts.Token); response.EnsureSuccessStatusCode(); using var responseStream = await response.Content.ReadAsStreamAsync(this._cts.Token); var weatherForecasts = JsonSerializer.DeserializeAsyncEnumerable<WeatherForecast>( responseStream, new JsonSerializerOptions { PropertyNameCaseInsensitive = true, DefaultBufferSize = 128 }, this._cts.Token); // 用WithCancellation将取消令牌绑定到枚举迭代,自动处理取消 await foreach (var weatherForecast in weatherForecasts.WithCancellation(this._cts.Token)) { forecasts.Add(weatherForecast); await InvokeAsync(StateHasChanged); await Task.Delay(1); } } catch (OperationCanceledException) { // 可在此添加取消后的UI提示或清理逻辑 } finally { this._cts.Dispose(); }
服务器端代码优化(可选)
无需大幅修改,确保[EnumeratorCancellation]标记的CancellationToken正确关联到请求取消信号即可,当前代码的检查逻辑已经足够:
async IAsyncEnumerable<WeatherForecast> Post([EnumeratorCancellation] CancellationToken cancellationToken, ILogger<Program> logger) { logger?.LogInformation($"Called Server"); var i = 0; try { while (!cancellationToken.IsCancellationRequested) { logger?.LogInformation($"Send item {i}"); cancellationToken.ThrowIfCancellationRequested(); yield return new WeatherForecast ( DateTime.Now.AddDays(i), Random.Shared.Next(-20, 55), WeatherForecastController.Summaries[Random.Shared.Next(WeatherForecastController.Summaries.Length)] ); await Task.Delay(1000, cancellationToken); i++; } } catch (OperationCanceledException) { logger?.LogInformation($"Server task cancelled"); // 可选:添加资源清理逻辑 } }
原理说明
WithCancellation会将取消令牌注入到异步枚举的迭代过程中,当客户端触发取消时,枚举器会立即抛出OperationCanceledException,终止迭代并释放响应流,进而关闭HTTP连接。- 服务器端的
[EnumeratorCancellation]令牌会在客户端断开连接时自动触发取消,此时Task.Delay或cancellationToken.ThrowIfCancellationRequested()会终止循环,结束服务器任务。
内容的提问来源于stack exchange,提问作者user20270550
相关产品推荐
相关产品推荐

