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

Blazor服务端渲染中IAsyncEnumerable<string>消费差异问题排查

问题:Blazor服务端渲染中本地与注入服务的IAsyncEnumerable DOM更新差异

在Blazor服务端渲染项目中,使用本地定义的IAsyncEnumerable<string>时,浏览器DOM可以实时更新:

<button type="button" @onclick="PopulateText">Click</button>
<p>@((MarkupString)_generatedText)</p>
@code {
    private string _generatedText = string.Empty;
    private async Task PopulateText()
    {
        await foreach (var text in Start())
        {
            _generatedText += text;
            StateHasChanged();
        }
    }

    private async IAsyncEnumerable<string> Start()
    {
        for (int i = 0; i < 10; i++)
        {
            await Task.Delay(500);
            yield return i.ToString();
        }
    }
}

但消费注入服务返回的IAsyncEnumerable<string>时,DOM仅在枚举完全生成后才更新。该服务通过HTTP请求调用OpenAI并以流式返回文本,对应代码如下:

<button type="button" @onclick="PopulateText">Click</button>
<p>@((MarkupString)_generatedText)</p>
@code {
    private string _generatedText = string.Empty;
    private async Task PopulateText()
    {
        await foreach (var text in _openAiService.GenerateText(new GenerateTextRequest("hei")))
        {
            _generatedText += text;
            StateHasChanged();
        }
    }
}

添加断点可见_generatedText已被赋值且StateHasChanged()已调用,但DOM仍无实时更新。原本期望后端调用能通过服务端渲染机制流式返回至客户端,但未实现。

已尝试的无效方案

Update 1:在服务方法中添加yield return

旧代码:

public IAsyncEnumerable<string> GenerateText(GenerateTextRequest request)
{
    return _generateService.GenerateTextAsync(request);
}

更新后代码:

public async IAsyncEnumerable<string> GenerateText(GenerateTextRequest request)
{
    await foreach (string text in _generateService.GenerateTextAsync(request))
    {
        yield return text;
    }
}

Update 2:重写ShouldRender()方法强制渲染DOM

private string _generatedText = string.Empty;
private bool _shouldForceRender = false;
private async Task PopulateText()
{
    await foreach (var text in _openAiService.GenerateText(new GenerateTextRequest("hei")))
    {
        _generatedText += text;
        _shouldForceRender = true;
        StateHasChanged();
    }
}

protected override bool ShouldRender()
{
    if (_shouldForceRender)
    {
        _shouldForceRender = false;
        return true;
    }

    return base.ShouldRender();
}

Update 3:使用模拟外部服务请求代码复现问题

public IAsyncEnumerable<string> GenerateText(GenerateTextRequest _)
{
    return TestServiceCall();
}

private async IAsyncEnumerable<string> TestServiceCall()
{
    var httpClient = new HttpClient();
    using var response = await httpClient.PostAsStreamAsync("https://httpbin.org/post", new { text = "test text" });
    await using var stream = await response.Content.ReadAsStreamAsync();
    using var reader = new StreamReader(stream);
    // 持续读取流直到结束
    while (!reader.EndOfStream)
    {
        var line = await reader.ReadLineAsync();
        // 跳过空行
        if (string.IsNullOrEmpty(line))
        {
            continue;
        }

        string text = null!;
        try
        {
            text += line;
        }
        catch (Exception)
        {
            line += await reader.ReadToEndAsync();
            text += line;
        }

        if (null != text)
        {
            yield return text;
        }
    }
}

请问为何这两种消费IAsyncEnumerable<string>的方式会被不同处理?


内容的提问来源于stack exchange,提问作者Stian Standahl

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 13:59:56