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

StreamReader.ReadLineAsync忽略单独回车符导致读取挂起问题求解

问题根因

你遇到的是StreamReader内置行读取逻辑的已知行为:当读取到单独的\r(回车符,ASCII 13)时,ReadLineAsync会等待下一个字符到达,以判断是否属于\r\n序列,不会直接将单独的\r判定为行结束。而网络流在对方没有发送新数据、也没有断开连接的情况下,读取操作会一直挂起,就出现了你遇到的问题。同时不建议在网络场景下使用EndOfStream属性做循环判断,该属性会触发阻塞读取来判定流是否结束,也会引发卡顿问题。

解决方案

自行实现支持单独\r作为行结束符的异步读取逻辑,替换原生的ReadLineAsync即可,实现代码如下:

自定义行读取扩展方法

using System.Text;

public static class StreamReaderExtensions
{
    public static async Task<string> ReadLineWithCrSupportAsync(this StreamReader reader, CancellationToken cancellationToken = default)
    {
        var stringBuilder = new StringBuilder();
        bool lastCharIsCr = false;

        while (true)
        {
            cancellationToken.ThrowIfCancellationRequested();
            
            char[] buffer = new char[1];
            int readLength = await reader.ReadAsync(buffer, 0, 1, cancellationToken);
            
            // 流已结束,返回已读取内容
            if (readLength == 0)
            {
                if (lastCharIsCr) stringBuilder.Append('\r');
                return stringBuilder.Length > 0 ? stringBuilder.ToString() : null;
            }

            char currentChar = buffer[0];

            // 上一个字符是\r的场景判断
            if (lastCharIsCr)
            {
                // 是\r\n序列,直接返回当前构建的行
                if (currentChar == '\n') return stringBuilder.ToString();
                // 是单独\r,将当前字符退回缓冲区,返回当前构建的行
                else
                {
                    var charBufferField = typeof(StreamReader).GetField("_charBuffer", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
                    var charPosField = typeof(StreamReader).GetField("_charPos", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
                    if (charBufferField != null && charPosField != null)
                    {
                        char[] charBuffer = (char[])charBufferField.GetValue(reader);
                        int charPos = (int)charPosField.GetValue(reader);
                        if (charPos > 0)
                        {
                            charPosField.SetValue(reader, charPos - 1);
                            charBuffer[charPos - 1] = currentChar;
                        }
                        else
                        {
                            stringBuilder.Append('\r');
                        }
                    }
                    else
                    {
                        stringBuilder.Append('\r');
                    }
                    return stringBuilder.ToString();
                }
            }

            if (currentChar == '\n') return stringBuilder.ToString();
            if (currentChar == '\r')
            {
                lastCharIsCr = true;
                continue;
            }

            stringBuilder.Append(currentChar);
        }
    }
}

修改后的服务端代码

using System.Net.Sockets;
using System.Text;

static async Task Main(string[] args)
{
    var listener = TcpListener.Create(22);
    listener.Start();
    // 增加取消令牌支持,方便程序优雅退出
    using var cts = new CancellationTokenSource();
    Console.CancelKeyPress += (s, e) =>
    {
        e.Cancel = true;
        cts.Cancel();
        listener.Stop();
    };

    try
    {
        var client = await listener.AcceptTcpClientAsync(cts.Token);
        using (var networkStream = client.GetStream())
        // 根据对接系统实际编码修改此处的编码参数
        using (var sr = new StreamReader(networkStream, Encoding.UTF8, leaveOpen: true))
        using (var sw = new StreamWriter(networkStream, Encoding.UTF8, leaveOpen: true) { AutoFlush = true })
        {
            try
            {
                while (!cts.IsCancellationRequested)
                {
                    var clientData = await sr.ReadLineWithCrSupportAsync(cts.Token);
                    // 客户端断开连接
                    if (clientData == null) break;

                    // 此处填写你的业务处理逻辑
                    Console.WriteLine($"收到行数据:{clientData}");
                }
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine("服务正在退出");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"业务处理异常:{ex.Message}");
            }
        }
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("服务已停止");
    }
}

额外优化建议

  • 给StreamWriter设置AutoFlush = true,避免写入的内容滞留在缓冲区未发送到网络
  • 如果对接的外部系统传输的是二进制数据而非纯文本,建议直接处理byte流而非用StreamReader做字符转换,避免编码转换导致的数据损坏
  • 可以根据业务需求增加读取超时逻辑,避免连接长时间空闲占用资源

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 02:45:05