【问题标题】:Fast Append Text to text box快速将文本附加到文本框
【发布时间】:2011-03-30 18:45:55
【问题描述】:

我有一个 BackgroundWorker 线程正在发布消息,在 GUI 的文本框中使用 BeginInvoke。在文本框中显示文本的方法write_debug_text 使用AppendText 并将文本写入Console。

外观是BackgroundWorker 写得太快,write_debug_text 跟不上。我在write_debug_text 设置了一个断点,并且必须等待很长时间才能被击中。许多对“BeginInvoke”的调用发生在断点被命中之前。

我正在寻找 UI 上消息的实时显示,就像 VS C# Express IDE 中的 System.Console。

通过搜索 SO,我了解到 AppendText 是使用更快的方法,并且可能必须重新分配字符串。

一些回复建议使用StringBuilder,然后定期将该文本写入文本框。但这需要添加更多的事件和计时器;我宁愿不这样做(我的 simple 应用程序变得越来越复杂)。

我怎样才能实时写入文本框(并让它显示)?

我目前的想法是创建一个从 Textbox 继承的小部件,它使用文本队列和计时器。

编辑 1:示例代码

这是我的代码片段:

    private m_textbox;
    //...
    m_textbox.BeginInvoke(new write_debug_text_callback(this.write_debug_text),
                                      new object[] { debug_text });
    return;

private void write_debug_text(string text)
{
    string time_stamp_text = "";
    time_stamp_text = DateTime.Now.ToString() + "   ";
    time_stamp_text += text;
    m_textbox.AppendText(time_stamp_text);
    m_textbox.Update();
    System.Console.Write(time_stamp_text);
    return;
}

我尝试将BeginInvoke 更改为Invoke,但我的应用程序已挂起。当我使用调试器暂停/中断时,执行指针正在调用Invoke。

顺便说一句,我在 Java、C++ 和 C 方面拥有多年经验。我在 C# 方面的第 5 个月。

【问题讨论】:

  • 如果您调整了应用程序中的任何线程优先级,则将它们全部设置为 Normal 可能会解决此问题。
  • @Jeffrey:不幸的是,我不知道如何设置BackgroundWorker、GUI 和SerialPort 的优先级;所以我没有改变它们的默认设置。

标签: c# logging textbox real-time appendtext


【解决方案1】:

如果显示的消息很多,那么问题很可能是内存分配问题。

假设每条消息有 30 个字符长。这将是(大约)60 个字节。让我们进一步假设您每秒添加 10 条消息。然后,在第一秒生成的字符串将是:60 + 120 + 60 + 180 + 60 + 240 + 60 + 300 + ... + 60 + 600 = 3840 字节。 在第二秒,总数上升到 13,740 字节。 第三秒:29,640。 第四名:51,540。 ... 第 10 位:308,940 字节。

18 秒后,达到 1 MB,显示的字符串为 11 kb。

在一分钟标记处,我们分配了 10 Mb 的字符串。 两分钟后,43 Mb 专用于这些消息,并且每个字符串的大小增加到 71 kb。 三分钟后,消息的大小超过 100 kb,其中有近 100 Mb 专用于它们。

这就是为什么 StringBuilder 对于构建长字符串如此重要的原因!

但由于您的计划是显示每个中间步骤,因此 StringBuilder 不会在这里帮助您。此 GUI 要求您生成一个公制船载字符串。

解决此问题的一种可能方法是在将数据插入到字符串的后面时将字符串的前面剪掉。您仍然会为字符串分配大量内存,但各个字符串本身的大小是有限的,因此,运行时在内存中找到它们的位置会容易得多,并且分配速率将往下走。

这应该会减少您的垃圾收集压力:

private const int maxDisplayTextLength = 5000;

private void write_debug_text(string text)
{
    string time_stamp_text = "";
    time_stamp_text = DateTime.Now.ToString() + "   " + text;
    string previous = m_textbox.Text;
    if (previous.Length + time_stamp_text.Length > maxDisplayTextLength)
         m_textbox.Text = previous.Substring(0, maxDisplayTextLength - time_stamp_text.Length) + time_stamp_text;
    else
         m_textbox.Text = previous + time_stamp_text;
    m_textbox.Update();
    System.Console.Write(time_stamp_text);
    return;
} 

【讨论】:

  • 是否可以让文本框使用(C++ 风格)对字符串的引用而不是复制它们?还是指针?
  • 另外,有没有办法启动垃圾收集器来重新获得一些碎片化的内存?
  • @Thomas Matthews - Re: C++ 风格,你也许可以用字符数组、不安全代码和 P/Invoke 做一些事情,但我认为你想要一个 简单申请!
  • @Thomas Matthews - 回复:垃圾收集器,GC 已经高度调整,您不太可能改进它的操作。它会在必要时运行,它会自动压缩分配的对象并对内存进行碎片整理。不幸的是,这不会改变正在使用的内存量的现实。无论如何,您可能正在经历“挂起”的 GC 操作。强制额外的挂起不会有任何帮助。
  • 我刚试过这个,并使用您提供的代码,它只是重用 textbox.text 的第一部分,直到 maxDisplayTextLength,然后将新消息附加到该部分。我认为您可能打算使用 previous.Remove() 来切掉所需的空间,然后将新的 time_stamp_text 附加到该空间,而不是 textbox.text 开头的子字符串。
【解决方案2】:

也许可以试试http://sourceforge.net/projects/fastlogconsole - 表现出色

【讨论】:

    【解决方案3】:

    如果您希望 实时 在文本框内写入,为什么不使用同步的 Invoke 而不是 BeginInvoke 将函数排队以便稍后调用?

    【讨论】:

    • BackgroundWorker 线程卡在Invoke 方法上(当我暂停调试器时,这是出现的行)。这是调用:m_textbox.Invoke(new write_debug_text_callback(this.write_debug_text), new object[] { debug_text });
    • 是否在 write_debug_text 中设置了断点?如果是这种情况,Visual Studio 将显示 UI 线程已停止在那里。 BackgroundWorker 线程将卡在 Invoke 上,因为 write_debug_text 必须完成才能执行其他任何操作。
    • 看起来我可能有死锁,因为Invoke,正在等待 UI 线程完成,但 UI 线程没有机会运行。
    【解决方案4】:

    不要使用BeginInvoke。请改用Invoke。见Application Becomes Nonresponsive While Adding Thousands of Rows。这不是完全相同的问题(他使用的是 DataGridView 而不是文本框),但它是同一类事情。你的BackgroundWorker 正在启动一大堆异步任务。你最好一次做一个。

    【讨论】:

    • 那么我该如何“一次做一个”呢?
    • 调用 Invoke 而不是 BeginInvoke 可以一次完成一个。
    【解决方案5】:

    您可以尝试改用RichTextBox 控件,并阻止它刷新其用户界面,除非每隔一段时间。有点像StringBuilder 的建议,但更简单一些。请参阅此SO question,了解如何执行此操作的示例,

    【讨论】:

      【解决方案6】:

      我使用了@Jeffre L. Whitledge 的建议并减少了字符串分配的数量。由于这是一个具有固定数量字符串的封闭应用程序,因此我缓存了这些字符串。这产生了我的程序执行速度明显更快的副作用。

      我的一个问题仍然是 Windows 响应消息的速度很慢。这可以在更新进度条时看到。从发送消息(例如附加文本)到执行消息之间存在一定的延迟。

      【讨论】:

      • 为了保持 GUI 窗口中的响应性,BeginInvoke 有一个过载,它接受 DispatcherPriority,如果您将其设置为低于“输入”(例如“背景”),则 BeginInvoke 调用不会干扰用户输入响应能力。
      猜你喜欢
      • 1970-01-01
      • 2020-03-12
      • 2013-08-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-17
      • 2014-11-05
      相关资源
      最近更新 更多