加速写入wpf中的文本框并滚动到结束?

speed up write to texbox in wpf with scroll to end?

我一直在寻找一种在 C#/WPF 中尽可能快地更新多行文本框的方法。所以我想我会和你们核实一下,看看你们是否可以给我一些 pointers/hints 或帮助。

我想要的是能够尽可能快地将尽可能多的数据输出到多行文本框,并在更新时滚动到结束,数据来自串行连接。我已经在 C#/Forms 中完成了这个,没有任何问题。但是在 WPF 中就没那么容易了。

所以如果我只写一行文本框,那么 5000 项需要 940 毫秒。 但对于多行,它需要 143 秒。 如果我不滚动到文本框的末尾,那么相同的数据需要 112 秒。

代码创建一个带有队列的 class 实例,用 5000 个项目填充队列,然后尝试尽快将数据输出到 UI->texbox。 我在输出中添加了秒表刻度,这样我就可以看到每个函数的时间。

显示的输出格式

ItemValue DQ-> 从开始添加到队列标记 Ev-> 从开始移除事件标记 TB- > 文本框从开始输出时间(ms),

Picture of the program

我整理了一个小测试程序,下面的代码与可以在这里下载的项目相同-> Link to project

测试用例 if 语句在 MainWindow:TxTextAdd()

我的想法是,有一种方法可以更改事件处理,以便它可以在每次 ui/event 处理程序触发时输出尽可能多的项目。所以我们可以一次打印多个项目而不是每个事件一个? 但我不知道如何实施该解决方案。

有什么想法吗?

enter 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Threading;
using System.Windows.Threading;

namespace BlockingCollectionAsFIFO
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {

        /// Create FIFO class
        FIFO_Class FIFOqueue = new FIFO_Class();

        // Display stopwatch
        Stopwatch DispStop = Stopwatch.StartNew();

        // Holds texbox string in one of the tests
        string TextBox_Text = "";

        public MainWindow()
        {
            InitializeComponent();

            // register UI update event to populate textbox from queue dequeue
            FIFOqueue.UI_Update += new FIFO_Class.UI_EventHandler(TxTextAdd);

            string ID = "A";

            /// Create new add thread in class
            for (int i = 0; i < 5; i++)
            {
                if(i == 1)
                {
                    ID = "B";
                }
                else if(i == 2)
                {
                    ID = "C";
                }
                else if (i == 3)
                {
                    ID = "D";
                }
                else if (i == 4)
                {
                    ID = "E";
                }
                // create thread
                FIFOqueue.CreateAddThread(ID);
            }

            /// Create new read thread in class
            FIFOqueue.CreateReadThread();
        }

        public void TxTextAdd(string Data)
        {
            Dispatcher.BeginInvoke(DispatcherPriority.Input, new ThreadStart(() =>
            {
                try
                {
                    /// Put togheter the string that we are going to display
                    Data += " TB->" + DispStop.ElapsedMilliseconds.ToString() + "ms";

                    /// true => only update the one row texbox with the latest string
                    if(true)
                    {
                        textbox2.Text = Data;
                    }
                    else
                    {
                        /// true => add string to TextBox_Text variable and display that (scroll to end)
                        /// false => add string to TextBox control and scroll to end
                        if (true)
                        {
                            TextBox_Text += Data + System.Environment.NewLine;
                            textbox.Text = TextBox_Text;
                        }
                        else
                        {
                            textbox.Text += Data + System.Environment.NewLine;
                            textbox.ScrollToEnd();
                        }
                    }
                }
                catch
                {}
            }));

        }

        private void btn_RUN_Click(object sender, RoutedEventArgs e)
        {

        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            /// Dispose alive dequeue thread
            FIFOqueue.Dispose();
        }
    }

    class FIFO_Class
    {
        // Main Queue
        private BlockingCollection<Package> queue = new BlockingCollection<Package>(new ConcurrentQueue<Package>());

        // Events
        public delegate void UI_EventHandler(string message);
        public event UI_EventHandler UI_Update;

        private Thread DeQueueThread;

        Stopwatch stop = Stopwatch.StartNew();


        public void CreateAddThread(string ID)
        {
            Thread t = new Thread(() => Add(ID));
            t.Start();
        }


        private void Add(string ID)
        {
            /// Just add some data
            for (int i = 0; i < 1000; i++)
            {
                Package pack = new Package();
                pack.ID = ID;
                pack.Data = i;
                /// Add pack into queue
                queue.Add(pack);
            }
        }

        public void CreateReadThread()
        {
            DeQueueThread = new Thread(ProcessPackets);
            DeQueueThread.Start();
        }

        private void ProcessPackets()
        {
            string Output;

            /// the try function exist so I can use abort thread
            try
            {
                while (true)
                {
                    /// Retrive a package from queue
                    Package e = queue.Take();
                    // check if there is any data in package
                    if (e != null)
                    {
                        /// Convert data from package to string, stopwatch puts number of ticks from the time the thread was created
                        Output = e.ID + e.Data.ToString() + " DQ-> " + stop.ElapsedTicks.ToString() + " ticks";
                        /// check if we have any subscribers on UIupdate event
                        UI_Update_Process(Output);
                    }
                }
            }
            catch (ThreadAbortException)
            {
            }

        }

        public void Dispose()
        {
            // Abort Thread.
            DeQueueThread.Abort();
            // Wait for the thread to terminate.
            DeQueueThread.Join();
        }

        // UI update Handler Event process
        protected void UI_Update_Process(string Data)
        {
            if (UI_Update != null)
            {
                //UI_Update(Data);
                UI_Update(Data + " Ev->" + stop.ElapsedTicks.ToString() + "ticks");
            }
        }
    }

    class Package
    {
        public string ID;
        public int Data;
    }
}

对于日志:

  1. Store/save文件中的日志。
  2. 在文本框旁边放置一个滚动条,并将其最大值设置为日志中的行数
  3. 每次滚动条更改时,都会从日志中获取正确的行,这些行将在文本框中可见(仅此而已)。
  4. 每次日志增长都会更改滚动条的最大值。

    • 这样你就可以尽可能多地虚拟化文本框
    • 您可以在 textbox/scrollbar 和日志文件之间放置一个缓冲区,以减少对日志的读取操作次数。
    • 您不需要向文本框添加 500 行;大多数文本框不会同时显示那么多行。