You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Blazor WASM中通过HttpClient消费IAsyncEnumerable时取消请求无效

Blazor HttpClient消费IAsyncEnumerable时取消请求不终止服务器任务的解决方法

问题描述

在Blazor页面中通过HttpClient消费服务器端返回的IAsyncEnumerable,数据流可正常接收,但点击按钮取消请求时,服务器端的异步枚举任务不会终止,仍持续生成数据。

关键现象

当服务器端代码仅包含Task.Delay循环(不使用yield return返回枚举项)时,客户端取消请求后,服务器的CancellationToken能正常触发取消;但使用yield return返回IAsyncEnumerable时,取消机制失效。

问题原因

  1. 客户端取消请求后,未及时终止异步枚举器的迭代,导致HTTP连接未立即关闭,服务器端无法感知客户端断开。
  2. 原客户端代码手动检查取消令牌后仅执行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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 08:35:21