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
相关产品推荐
相关产品推荐

