如何提高FlowDocumentScrollViewer的性能?

在上一个问题中,我问过如何在类似WPF文本框的元素中获取实时日志输出( WPF附加文本块大量阻止UI线程但WinForms没有? )。 那里的答案让我使用了FlowDocumentScrollViewer ,它确实比RichTextBox快得多。 但是,我发现运行具有大量文本输出的命令(如’svn co’)导致我的WPF应用程序明显减慢。 检查3或4个非常大的svn分支后切换标签需要3-4秒,我确信时间会跟我检查的数量一起增加。 滚动也有明显的滞后。

正如我在上面链接的问题中所述,我最近将我的应用程序从Windows窗体切换到了WPF。 我非常喜欢WPF–它提供了许多我在Forms中没有的优点。 然而,至少在我看来,性能在WPF中似乎是一个相当大的问题。 在我的应用程序的Forms版本中,我可以向RichTextBox控件打印大量文本,并且在我的应用程序中根本没有减速。 切换标签是即时的,滚动是无缝的。 这是我在WPF应用程序中想要的体验。

所以我的问题是:如何提高FlowDocumentScrollViewer的性能以匹配Windows窗体RichTextBox的性能,而不会丢失粗体和斜体等格式化function,并且不会丢失复制/粘贴function? 我愿意切换WPF控件,只要它们提供我正在寻找的格式化function。

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

 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 output; public BlockingCollection errors; public BlockingCollection 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; } } } 

MainWindow.xaml

                                           

MainWindow.xaml.cs

 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 outputProducer = new BlockingCollection(); public BlockingCollection errorProducer = new BlockingCollection(); public BlockingCollection logsProducer = new BlockingCollection(); public ObservableCollection Output { get; set; } public ObservableCollection Errors { get; set; } public ObservableCollection 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(); Errors = new ObservableCollection(); Logs = new ObservableCollection(); 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 producer, ObservableCollection 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 allItems = new HashSet(box.Items.OfType()); HashSet selectedItems = new HashSet(box.SelectedItems.OfType()); IEnumerable sortedItems = allItems.Where(i => selectedItems.Contains(i)); IEnumerable copyItems = from i in sortedItems select i.Text; string log = string.Join("\r\n", copyItems); Clipboard.SetText(log); } } } 

ListBoxSelector.cs在@ pushpraj的答案中。

我执行你提供的样本,除了几个线程问题,最大的问题是数据量。 随着文本呈现的增长,它会减慢文本呈现速度。

我尝试以不同的方式重写您的代码。 我使用Tasks,BlockingCollection和Virtualization来提高性能,假设应用程序的主要兴趣是记录速度。

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 output; public BlockingCollection impOutput; public BlockingCollection errors; public BlockingCollection impErrors; public BlockingCollection 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 } } 

MainWindow.xaml

                                 

MainWindow.xaml.cs

 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 outputProducer = new BlockingCollection(); public BlockingCollection impOutputProducer = new BlockingCollection(); public BlockingCollection errorProducer = new BlockingCollection(); public BlockingCollection impErrorProducer = new BlockingCollection(); public BlockingCollection logsProducer = new BlockingCollection(); public ObservableCollection Output { get; set; } public ObservableCollection Errors { get; set; } public ObservableCollection 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(); Errors = new ObservableCollection(); Logs = new ObservableCollection(); Task.Run(() => Print(outputProducer, Output)); Task.Run(() => Print(errorProducer, Errors)); Task.Run(() => Print(logsProducer, Logs)); } public void Print(BlockingCollection producer, ObservableCollection 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; } } } 

要获得完整的工作示例,请下载PerformanceTest.zip ,看看它是否接近您的需求。 我只重写了一部分。 如果此示例指向期望的方向,我们可以实现其余function。 取消评论Task.Delay(1).Wait(); 在Bridge.cs中如果您可能想要减慢生产速度以查看混合日志,否则日志生成太快,因此它在日志选项卡中一个接一个地出现。

使用FlowDocumentPageViewer将有助于提高性能,因为它异步加载文档。