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

C#多线程场景下RichTextBox.AppendText(Environment.NewLine)出两次换行如何解决?

问题原因分析
  • 首先可以排除多线程调度的影响:你用Dispatcher.Invoke是同步将操作投递到UI线程执行,不会出现重复执行、乱序的问题,和多线程逻辑无关。
  • 核心原因1:WPF的RichTextBox默认的Paragraph段落样式自带上下边距,你插入的Environment.NewLine本身是一个换行,加上段落默认的间距,视觉上就会呈现出双倍换行的效果。
  • 核心原因2:Windows环境下Environment.NewLine的实际值是\r\n(回车+换行),部分版本的WPF RichTextBox会把\r和\n分别解析为两次换行操作,最终也会出现两个换行的问题。
代码修改建议

1. 修复双倍换行

两种方案二选一即可:

  • 方案一:将AppendText(Environment.NewLine)替换为AppendText("\n"),避免\r\n被识别为两次换行。
  • 方案二:修改RichTextBox的默认段落样式,去掉段落自带的上下边距,在XAML中添加如下配置即可:
<RichTextBox x:Name="RichTextBox_logs">
    <RichTextBox.Resources>
        <Style TargetType="Paragraph">
            <Setter Property="Margin" Value="0"/>
        </Style>
    </RichTextBox.Resources>
</RichTextBox>

如果不想修改XAML,也可以在后台代码初始化时添加:

RichTextBox_logs.Resources.Add(typeof(Paragraph), new Style(typeof(Paragraph)) 
{ 
    Setters = { new Setter(Paragraph.MarginProperty, new Thickness(0)) } 
});

2. 优化业务逻辑

你当前代码单次循环内多次调用Dispatcher.Invoke,频繁调度会增加UI线程开销,可以合并UI操作,同时添加请求间隔避免被接口封禁,修改后参考代码如下:

private void getMyIPAddress()
{
    string Address = "";
    Dispatcher.Invoke(() =>
    {
        RichTextBox_logs.VerticalScrollBarVisibility = ScrollBarVisibility.Visible;
        // 初始化段落样式
        RichTextBox_logs.Resources.Add(typeof(Paragraph), new Style(typeof(Paragraph)) 
        { 
            Setters = { new Setter(Paragraph.MarginProperty, new Thickness(0)) } 
        });
    });
    while (true)
    {
        string logContent = "";
        string currentIp = "";
        bool needUpdateIp = false;
        WebRequest request = WebRequest.Create("http://checkip.dyndns.com/");
        try
        {
            using (WebResponse response = request.GetResponse())
            {
                using (StreamReader stream = new StreamReader(response.GetResponseStream()))
                {
                    Address = stream.ReadToEnd();
                }
                int first = Address.IndexOf("Address: ") + 9;
                int last = Address.IndexOf("</body>");               
                string newIp = Address.Substring(first, last - first);
                if (CurrentAddressHolder != newIp)
                {
                    CurrentAddressHolder = newIp;
                    needUpdateIp = true;
                    currentIp = newIp;
                }
                logContent = $"\nIP is {CurrentAddressHolder}";
            }
        }
        catch(Exception e)
        {
            logContent = $"\n{e.ToString()}";
        }
        // 合并所有UI操作到一次调度
        Dispatcher.Invoke(() =>
        {
            RichTextBox_logs.AppendText(logContent);
            if (needUpdateIp)
            {
                textBox_ip.Text = currentIp;
            }
            RichTextBox_logs.ScrollToEnd();// 自动滚动到底部查看最新日志
        });
        // 添加请求间隔,示例为1分钟请求一次,可自行调整
        Thread.Sleep(1000 * 60);
    }
}
额外优化方案
  • 可以用async/await+Task.Delay代替Thread.Sleep,不会阻塞线程,性能更好。
  • 日志内容过多时可以定期清理RichTextBox的历史内容,避免内存占用过高。
  • 可以添加循环退出标识,程序关闭时主动终止循环,避免后台死线程残留。

内容的提问来源于stack exchange,提问作者Jensar Joey Sayson

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 11:36:05