C# 如何提高FlowDocumentScrollViewer的性能?

C# 如何提高FlowDocumentScrollViewer的性能?,c#,wpf,performance,text,C#,Wpf,Performance,Text,在前面的问题中,我问了如何在类似WPF文本框的元素()中获得实时日志输出。这里的答案让我使用了FlowDocumentScrollViewer,它确实比RichTextBox快得多。但是,我发现运行具有大量文本输出的命令(如“svnco”)会导致WPF应用程序的速度明显减慢。签出3或4个非常大的svn分支后切换选项卡需要3-4秒,我相信时间会随着签出的次数而增加。滚动也有明显的滞后 正如我在上面链接的问题中所述,我最近将我的应用程序从Windows窗体切换到了WPF。我非常喜欢WPF——它给了我

在前面的问题中,我问了如何在类似WPF文本框的元素()中获得实时日志输出。这里的答案让我使用了
FlowDocumentScrollViewer
,它确实比
RichTextBox
快得多。但是,我发现运行具有大量文本输出的命令(如“svnco”)会导致WPF应用程序的速度明显减慢。签出3或4个非常大的svn分支后切换选项卡需要3-4秒,我相信时间会随着签出的次数而增加。滚动也有明显的滞后

正如我在上面链接的问题中所述,我最近将我的应用程序从Windows窗体切换到了WPF。我非常喜欢WPF——它给了我很多表单中没有的优势。然而,性能在WPF中似乎是一个相当大的问题,至少对我来说是这样。在我的应用程序的表单版本中,我可以将大量文本打印到
RichTextBox
控件,在我的应用程序中一点速度也没有变慢。切换标签是即时的,滚动是无缝的。这就是我希望在WPF应用程序中获得的体验

因此,我的问题是:如何提高我的
FlowDocumentScrollViewer
的性能,以与Windows窗体
RichTextBox
的性能相匹配,而不丢失粗体和斜体等格式功能,也不丢失复制/粘贴功能?我愿意切换WPF控件,只要它们提供我想要的格式化功能

这是我的打印代码,供参考:

public void PrintOutput(String s)
{
    if (outputParagraph.FontSize != defaultFontSize)
    {
        outputParagraph = new Paragraph();
        outputParagraph.Margin = new Thickness(0);
        outputParagraph.FontFamily = font;
        outputParagraph.FontSize = defaultFontSize;
        outputParagraph.TextAlignment = TextAlignment.Left;
        OutputBox.Document.Blocks.Add(outputParagraph);
    }
    outputParagraph.Inlines.Add(s);
    if (!clearOutputButton.IsEnabled) clearOutputButton.IsEnabled = true;
}

public void PrintImportantOutput(String s)
{
    if (outputParagraph.FontSize != importantFontSize)
    {
        outputParagraph = new Paragraph();
        outputParagraph.Margin = new Thickness(0);
        outputParagraph.FontFamily = font;
        outputParagraph.FontSize = importantFontSize;
        outputParagraph.TextAlignment = TextAlignment.Left;
        OutputBox.Document.Blocks.Add(outputParagraph);
    }
    String timestamp = DateTime.Now.ToString("[hh:mm.ss] ");
    String toPrint = timestamp + s;
    outputParagraph.Inlines.Add(new Bold(new Run(toPrint)));
    if (!clearOutputButton.IsEnabled) clearOutputButton.IsEnabled = true;
}
在打印“重要”文本时,我切换字体大小并将文本加粗。这段代码之所以有这么多行,是因为我试图对所有文本重复使用相同的段落,直到我点击“重要”文本;我添加一个包含所有“重要”文本的新段落,然后在切换回非重要文本后添加另一个段落,并附加到该段落,直到找到更“重要”的文本。我希望重复使用同一段可以提高性能

另外,应该注意的是,我正在将stdout打印到一个
FlowDocumentScrollViewer
,将stderr打印到另一个
FlowDocumentScrollViewer
,并同时将两者打印到第三个
FlowDocumentScrollViewer
。因此,从技术上讲,每行stdout和stderr都会打印两次,使我的应用程序的负载增加了一倍。同样,这在WinForms中不是问题


下面是注释中要求的完整代码示例。它非常简单(3个FlowDocumentScrollViewer和简单的打印),但仍然大大降低了大约20000行文本的速度,而且比这更糟糕

编辑:代码示例已被删除。取而代之的是解决性能问题的工作代码。它的工作原理与
FlowDocumentScrollViewer
相同,但有一个例外:不能选择行的子字符串。我正在考虑解决这个问题,尽管看起来很难

Bridge.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Documents;
using System.Threading;
using System.Collections.Concurrent;
using System.Threading.Tasks;

namespace PerformanceTest
{
    public class Bridge
    {
        int counterLimit;

        public BlockingCollection<PrintInfo> output;
        public BlockingCollection<PrintInfo> errors;
        public BlockingCollection<PrintInfo> logs;


        protected static Bridge controller = new Bridge();

        public static Bridge Controller
        {
            get
            {
                return controller;
            }
        }

        public MainWindow Window
        {
            set
            {
                if (value != null)
                {
                    output = value.outputProducer;
                    errors = value.errorProducer;
                    logs = value.logsProducer;
                }
            }
        }

        public bool Running
        {
            get;
            set;
        }

        private Bridge()
        {
            //20000 lines seems to slow down tabbing enough to prove my point.
            //increase this number to get even worse results.
            counterLimit = 40000;
        }

        public void PrintLotsOfText()
        {
            new Thread(new ThreadStart(GenerateOutput)).Start();
            new Thread(new ThreadStart(GenerateError)).Start();
        }

        private void GenerateOutput()
        {
            //There is tons of output text, so print super fast if possible.
            int counter = 1;
            while (Running && counter < counterLimit)
            {
                if (counter % 10 == 0)
                    PrintImportantOutput("I will never say this horrible word again as long I live. This is confession #" + counter++ + ".");
                else
                    PrintOutput("I will never say this horrible word again as long I live. This is confession #" + counter++ + ".");
                //Task.Delay(1).Wait();
            }
            Console.WriteLine("GenerateOutput thread should end now...");
        }

        private void GenerateError()
        {
            int counter = 1;
            while (Running && counter < counterLimit)
            {
                PrintError("I will never forgive your errors as long I live. This is confession #" + counter++ + ".");
                //Task.Delay(1).Wait();
            }
            Console.WriteLine("GenerateError thread should end now...");
        }

        #region Printing
        delegate void StringArgDelegate(String s);
        delegate void InlineArgDelegate(Inline inline);
        public void PrintOutput(String s)
        {
            output.TryAdd(new PrintInfo(s, false));
            PrintLog("d " + s);
        }

        public void PrintImportantOutput(String s)
        {
            output.TryAdd(new PrintInfo(s, true));
            PrintLog("D " + s);
        }

        public void PrintError(String s)
        {
            errors.TryAdd(new PrintInfo(s, false));
            PrintLog("e " + s);
        }

        public void PrintImportantError(String s)
        {
            errors.TryAdd(new PrintInfo(s, true));
            PrintLog("E " + s);
        }

        public void PrintLog(String s)
        {
            logs.TryAdd(new PrintInfo(s, false));
        }
        #endregion
    }

    public class PrintInfo
    {
        public String Text { get; set; }
        public bool IsImportant { get; set; }

        public PrintInfo() { }
        public PrintInfo(String text, bool important)
        {
            Text = text;
            IsImportant = important;
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Documents;
using System.Threading;
using System.Collections.Concurrent;
using System.Threading.Tasks;

namespace PerformanceTest
{
    public class Bridge
    {
        int counterLimit;

        public BlockingCollection<string> output;
        public BlockingCollection<string> impOutput;
        public BlockingCollection<string> errors;
        public BlockingCollection<string> impErrors;
        public BlockingCollection<string> logs;


        protected static Bridge controller = new Bridge();

        public static Bridge Controller
        {
            get
            {
                return controller;
            }
        }

        public MainWindow Window
        {
            set
            {
                if (value != null)
                {
                    output = value.outputProducer;
                    impOutput = value.impOutputProducer;
                    errors = value.errorProducer;
                    impErrors = value.impErrorProducer;
                    logs = value.logsProducer;
                }
            }
        }

        public bool Running
        {
            get;
            set;
        }

        private Bridge()
        {
            //20000 lines seems to slow down tabbing enough to prove my point.
            //increase this number to get even worse results.
            counterLimit = 40000;
        }

        public void PrintLotsOfText()
        {
            Task.Run(() => GenerateOutput());
            Task.Run(() => GenerateError());
        }

        private void GenerateOutput()
        {
            //There is tons of output text, so print super fast if possible.
            int counter = 1;
            while (Running && counter < counterLimit)
            {
                PrintOutput("I will never say this horrible word again as long I live. This is confession #" + counter++ + ".");
                //Task.Delay(1).Wait();
            }
        }

        private void GenerateError()
        {
            int counter = 1;
            while (Running && counter < counterLimit)
            {
                PrintError("I will never forgive your errors as long I live. This is confession #" + counter++ + ".");
                //Task.Delay(1).Wait();
            }
        }

        #region Printing
        delegate void StringArgDelegate(String s);
        delegate void InlineArgDelegate(Inline inline);
        public void PrintOutput(String s)
        {
            output.TryAdd(s);
            PrintLog("d " + s);
        }

        public void PrintImportantOutput(String s)
        {
            impOutput.TryAdd(s);
            PrintLog("D " + s);
        }

        public void PrintError(String s)
        {
            errors.TryAdd(s);
            PrintLog("e " + s);
        }

        public void PrintImportantError(String s)
        {
            impErrors.TryAdd(s);
            PrintLog("E " + s);
        }

        public void PrintLog(String s)
        {
            String text = s;
            logs.TryAdd(text);
        }
        #endregion
    }
}

使用制度;
使用System.Collections.Generic;
使用System.Linq;
使用系统文本;
使用System.Windows.Documents;
使用系统线程;
使用System.Collections.Concurrent;
使用System.Threading.Tasks;
命名空间性能测试
{
公共级桥梁
{
int反极限;
公共封锁收集输出;
公共封锁收集错误;
公开封锁收集日志;
受保护的静态网桥控制器=新网桥();
公共静态电桥控制器
{
得到
{
返回控制器;
}
}
公共主窗口
{
设置
{
if(值!=null)
{
输出=值。输出生产者;
错误=value.errorProducer;
logs=value.logsProducer;
}
}
}
公营学校
{
得到;
设置
}
私人桥梁()
{
//两万行似乎放慢了切换速度,足以证明我的观点。
//增加这个数字会得到更糟糕的结果。
反限额=40000;
}
public void PrintLotsOfText()
{
新线程(新线程开始(生成输出)).Start();
新线程(新线程开始(GenerateError)).Start();
}
私有void GenerateOutput()
{
//有成吨的输出文本,所以如果可能,打印速度会非常快。
int计数器=1;
同时(运行和计数器<计数器极限)
{
如果(计数器%10==0)
PrintImportantOutput(“只要我活着,我就再也不会说这个可怕的词了。这是忏悔”+“counter++”);
其他的
打印输出(“只要我活着,我就再也不会说这个可怕的词了。这是自白”+“计数器+++”);
//Task.Delay(1.Wait();
}
WriteLine(“生成输出线程现在应该结束…”);
}
私有void生成器错误()
{
int计数器=1;
同时(运行和计数器<计数器极限)
{
PrintError(“我永远不会原谅你的错误。这是忏悔”#“+计数器+++”);
//Task.Delay(1.Wait();
}
WriteLine(“GenerateError线程现在应该结束…”);
}
#区域打印
委托无效字符串argdelegate(字符串s);
delegate void InlineArgDelegate(Inline-Inline);
公共无效打印输出(字符串s)
{
output.TryAdd(新的PrintInfo(s,false));
打印日志(“d”+s);
}
public void PrintImportantOutput(字符串s)
{
output.TryAdd(新的PrintInfo(s,true));
打印日志(“D”+s);
}
公共void打印错误(字符串s)
{
errors.TryAdd

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
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.Windows.Threading;
using System.Threading;

namespace PerformanceTest
{
    /// 
    /// Interaction logic for MainWindow.xaml
    /// 
    public partial class MainWindow : Window
    {
        public BlockingCollection<PrintInfo> outputProducer = new BlockingCollection<PrintInfo>();
        public BlockingCollection<PrintInfo> errorProducer = new BlockingCollection<PrintInfo>();
        public BlockingCollection<PrintInfo> logsProducer = new BlockingCollection<PrintInfo>();

        public ObservableCollection<PrintInfo> Output { get; set; }
        public ObservableCollection<PrintInfo> Errors { get; set; }
        public ObservableCollection<PrintInfo> Logs { get; set; }

        protected FontFamily font = new FontFamily("Consolas");
        protected int defaultFontSize = 12;
        protected int importantFontSize = 14;

        Dispatcher dispatcher;

        public MainWindow()
        {
            Bridge.Controller.Window = this;
            try
            {
                InitializeComponent();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.InnerException.ToString());
                Console.WriteLine(ex.StackTrace);
            }

            dispatcher = Dispatcher;
            Output = new ObservableCollection<PrintInfo>();
            Errors = new ObservableCollection<PrintInfo>();
            Logs = new ObservableCollection<PrintInfo>();


            new Thread(new ThreadStart(() => Print(outputProducer, Output))).Start();
            new Thread(new ThreadStart(() => Print(errorProducer, Errors))).Start();
            new Thread(new ThreadStart(() => Print(logsProducer, Logs))).Start();
        }

        public delegate void EmptyDelegate();

        public void Print(BlockingCollection<PrintInfo> producer, ObservableCollection<PrintInfo> target)
        {
            try
            {
                foreach (var info in producer.GetConsumingEnumerable())
                {
                    dispatcher.Invoke(new EmptyDelegate(() =>
                    {
                        if (info.IsImportant)
                        {
                            String timestamp = DateTime.Now.ToString("[hh:mm.ss] ");
                            String toPrint = timestamp + info.Text;
                            info.Text = toPrint;
                        }
                        target.Add(info);
                    }), DispatcherPriority.Background);
                }
            }
            catch (TaskCanceledException)
            {
                //window closing before print finish
            }
        }

        private void StartButton_Click(object sender, RoutedEventArgs e)
        {
            if (!Bridge.Controller.Running)
            {
                Bridge.Controller.Running = true;
                Bridge.Controller.PrintLotsOfText();
            }
        }

        private void EndButton_Click(object sender, RoutedEventArgs e)
        {
            Bridge.Controller.Running = false;
        }

        private void CopyExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            ListBox box = sender as ListBox;

            HashSet<PrintInfo> allItems = new HashSet<PrintInfo>(box.Items.OfType<PrintInfo>());
            HashSet<PrintInfo> selectedItems = new HashSet<PrintInfo>(box.SelectedItems.OfType<PrintInfo>());

            IEnumerable<PrintInfo> sortedItems = allItems.Where(i => selectedItems.Contains(i));
            IEnumerable<String> copyItems = from i in sortedItems select i.Text;

            string log = string.Join("\r\n", copyItems);
            Clipboard.SetText(log);
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Documents;
using System.Threading;
using System.Collections.Concurrent;
using System.Threading.Tasks;

namespace PerformanceTest
{
    public class Bridge
    {
        int counterLimit;

        public BlockingCollection<string> output;
        public BlockingCollection<string> impOutput;
        public BlockingCollection<string> errors;
        public BlockingCollection<string> impErrors;
        public BlockingCollection<string> logs;


        protected static Bridge controller = new Bridge();

        public static Bridge Controller
        {
            get
            {
                return controller;
            }
        }

        public MainWindow Window
        {
            set
            {
                if (value != null)
                {
                    output = value.outputProducer;
                    impOutput = value.impOutputProducer;
                    errors = value.errorProducer;
                    impErrors = value.impErrorProducer;
                    logs = value.logsProducer;
                }
            }
        }

        public bool Running
        {
            get;
            set;
        }

        private Bridge()
        {
            //20000 lines seems to slow down tabbing enough to prove my point.
            //increase this number to get even worse results.
            counterLimit = 40000;
        }

        public void PrintLotsOfText()
        {
            Task.Run(() => GenerateOutput());
            Task.Run(() => GenerateError());
        }

        private void GenerateOutput()
        {
            //There is tons of output text, so print super fast if possible.
            int counter = 1;
            while (Running && counter < counterLimit)
            {
                PrintOutput("I will never say this horrible word again as long I live. This is confession #" + counter++ + ".");
                //Task.Delay(1).Wait();
            }
        }

        private void GenerateError()
        {
            int counter = 1;
            while (Running && counter < counterLimit)
            {
                PrintError("I will never forgive your errors as long I live. This is confession #" + counter++ + ".");
                //Task.Delay(1).Wait();
            }
        }

        #region Printing
        delegate void StringArgDelegate(String s);
        delegate void InlineArgDelegate(Inline inline);
        public void PrintOutput(String s)
        {
            output.TryAdd(s);
            PrintLog("d " + s);
        }

        public void PrintImportantOutput(String s)
        {
            impOutput.TryAdd(s);
            PrintLog("D " + s);
        }

        public void PrintError(String s)
        {
            errors.TryAdd(s);
            PrintLog("e " + s);
        }

        public void PrintImportantError(String s)
        {
            impErrors.TryAdd(s);
            PrintLog("E " + s);
        }

        public void PrintLog(String s)
        {
            String text = s;
            logs.TryAdd(text);
        }
        #endregion
    }
}
<Window x:Class="PerformanceTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"
        WindowStartupLocation="CenterScreen">
    <Grid>
        <TabControl>
            <TabControl.Resources>
                <Style TargetType="ListBox">
                    <Setter Property="TextElement.FontFamily"
                            Value="Consolas" />
                    <Setter Property="TextElement.FontSize"
                            Value="12" />
                    <Setter Property="VirtualizingPanel.IsVirtualizing"
                            Value="True" />
                    <Setter Property="VirtualizingPanel.VirtualizationMode"
                            Value="Recycling" />
                </Style>
            </TabControl.Resources>
            <TabItem Header="Bridge">
                <StackPanel Orientation="Vertical"
                            HorizontalAlignment="Left">
                    <Button Content="Start Test"
                            Click="StartButton_Click" />
                    <Button Content="End Test"
                            Click="EndButton_Click" />
                </StackPanel>
            </TabItem>
            <TabItem Header="Output">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*" />
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="*" />
                    </Grid.ColumnDefinitions>
                    <ListBox Grid.Column="0"
                             ItemsSource="{Binding Output,RelativeSource={RelativeSource FindAncestor,AncestorType=Window}}" />

                    <GridSplitter Grid.Column="1"
                                  Width="5"
                                  ResizeBehavior="PreviousAndNext" />
                    <ListBox Grid.Column="2"
                             ItemsSource="{Binding Errors,RelativeSource={RelativeSource FindAncestor,AncestorType=Window}}" />
                </Grid>

            </TabItem>
            <TabItem Header="Log">
                <Grid>
                    <ListBox Grid.Column="0"
                             ItemsSource="{Binding Logs,RelativeSource={RelativeSource FindAncestor,AncestorType=Window}}" />
                </Grid>
            </TabItem>
        </TabControl>
    </Grid>
</Window>
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
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.Windows.Threading;

namespace PerformanceTest
{
    /// 
    /// Interaction logic for MainWindow.xaml
    /// 
    public partial class MainWindow : Window
    {
        public BlockingCollection<string> outputProducer = new BlockingCollection<string>();
        public BlockingCollection<string> impOutputProducer = new BlockingCollection<string>();
        public BlockingCollection<string> errorProducer = new BlockingCollection<string>();
        public BlockingCollection<string> impErrorProducer = new BlockingCollection<string>();
        public BlockingCollection<string> logsProducer = new BlockingCollection<string>();

        public ObservableCollection<object> Output { get; set; }
        public ObservableCollection<object> Errors { get; set; }
        public ObservableCollection<object> Logs { get; set; }

        Dispatcher dispatcher;

        public MainWindow()
        {
            Bridge.Controller.Window = this;
            try
            {
                InitializeComponent();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.InnerException.ToString());
                Console.WriteLine(ex.StackTrace);
            }

            dispatcher = Dispatcher;
            Output = new ObservableCollection<object>();
            Errors = new ObservableCollection<object>();
            Logs = new ObservableCollection<object>();
            Task.Run(() => Print(outputProducer, Output));
            Task.Run(() => Print(errorProducer, Errors));
            Task.Run(() => Print(logsProducer, Logs));
        }

        public void Print(BlockingCollection<string> producer, ObservableCollection<object> target)
        {
            try
            {
                foreach (var str in producer.GetConsumingEnumerable())
                {
                    dispatcher.Invoke(() =>
                    {
                        target.Insert(0, str);
                    }, DispatcherPriority.Background);
                }
            }
            catch (TaskCanceledException)
            {
                //window closing before print finish
            }
        }

        private void StartButton_Click(object sender, RoutedEventArgs e)
        {
            if (!Bridge.Controller.Running)
            {
                Bridge.Controller.Running = true;
                Bridge.Controller.PrintLotsOfText();
            }
        }

        private void EndButton_Click(object sender, RoutedEventArgs e)
        {
            Bridge.Controller.Running = false;
        }
    }
}