【问题标题】:Streaming lines of text using IAsyncEnumerable使用 IAsyncEnumerable 流式传输文本行
【发布时间】:2021-11-16 16:23:03
【问题描述】:

我想将任意数量的纯文本行从 ASP.NET 服务器流式传输到 Blazor WebAssembly 客户端 (.NET 6.0)。

为了测试,我实现了以下虚拟 API:

[HttpGet("lines")]
public async IAsyncEnumerable<string> GetLines() {
    for (var i = 0; i < 10; ++i) {
        yield return "test\n";
        await Task.Delay(1000);
    }
}

在客户端我尝试了以下方法(遵循these ideas):

public async IAsyncEnumerable<string?> GetLines() {
    var response = await HttpClient.GetAsync($"{apiRoot}/lines", HttpCompletionOption.ResponseHeadersRead);
    if (response.IsSuccessStatusCode) {
        var responseStream = await response.Content.ReadAsStreamAsync();
        var lines = JsonSerializer.DeserializeAsyncEnumerable<string>(responseStream);
        await foreach (var line in lines) {
            yield return line;
        }
    }
    else {
        Log.Error($"Server response code: {response.StatusCode}");
        yield return null;
    }
}

不幸的是,response.Content.ReadAsStreamAsync() 不是立即返回,而是缓冲整个流(即 10 行“test\n”),在这种情况下需要 10 秒,然后缓冲的内容被反序列化为 IAsyncEnumerable&lt;string&gt;。

使用HttpClient.GetStreamAsync 可以观察到相同的行为:

public async IAsyncEnumerable<string?> GetLines() {
    var responseStream = await HttpClient.GetStreamAsync($"{apiRoot}/lines"); // buffers for 10 s
    var linesAsync = JsonSerializer.DeserializeAsyncEnumerable<string>(responseStream);
    await foreach (var line in lines) {
        yield return line;
    }
}

如何更改此设置,以便从服务器发送的每一行都立即在客户端上处理,而无需任何缓冲? 这是客户端还是服务器端的问题?有没有办法禁用这种缓冲行为?

编辑:经过更多实验,我发现直接调用 API(例如通过浏览器)确实显示了预期的流式传输行为,即各个行一个接一个地弹出,并带有1.0 秒延迟。所以这似乎确实是一个客户端问题。

【问题讨论】:

  • 那么,您想在获得下一条之前先获得一条线路并做一些事情吗?如果是这样,那么您可能需要将其作为单独的 Web 请求来执行 - 据我所知,您必须等待完整的响应才能处理它。或者,您的 API 上的 Task.Delay 可能只是阻塞。
  • 只是为了确定:您确定缓冲发生在您突出显示的行上吗?
  • 你的服务器也是.NET 6吗?
  • 您是否也按照the article you referenced 中的说明设置了DefaultBufferSize?
  • @canton7 这是我使用调试器观察到的,是的。

标签: c# asp.net streaming blazor-webassembly iasyncenumerable


【解决方案1】:

我找到了一个适合我的解决方法,因为我不需要任何 JSON 反序列化,因为我想流式传输原始字符串。

以下实现解决了客户端流式传输问题:

public async IAsyncEnumerable<string?> GetLines() {
    using var request = new HttpRequestMessage(HttpMethod.Get, $"{apiRoot}/lines");
    request.SetBrowserResponseStreamingEnabled(true);
    var response = await HttpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);

    if (response.IsSuccessStatusCode) {
        using var responseStream = await response.Content.ReadAsStreamAsync();
        using var reader = new StreamReader(responseStream);
        string? line = null;
        while ((line = await reader.ReadLineAsync()) != null) {
            yield return line;
        }
    }
    else {
        Log.Error($"Server response code: {response.StatusCode}");
        yield return null;
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-09-08
    • 1970-01-01
    • 1970-01-01
    • 2018-08-01
    • 1970-01-01
    • 2014-05-09
    • 2011-12-04
    相关资源
    最近更新 更多