在上一个问题中,我问过如何在类似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
,而不会丢失复制/粘贴功能?我愿意切换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的每一行在技术上都被打印两次,使我的app上的负载加倍.同样,这不是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 BlockingCollectionoutput; 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 BlockingCollectionoutputProducer = 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<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 } }
MainWindow.xaml
<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 /> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <ListBox Grid.Column="0" ItemsSource="{Binding Output,RelativeSource={RelativeSource FindAncestor,AncestorType=Window}}" /> <GridSplitter Grid.Column="1" 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>
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<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; } } }
有关完整的工作示例,请下载PerformanceTest.zip,看看它是否接近您的需求.我只重写了一部分.如果此示例指向期望的方向,我们可以实现其余功能.Task.Delay(1).Wait();
如果您可能希望减慢生产速度以查看混合日志,则在Bridge.cs中取消注释,否则日志生成速度过快,因此它会在日志选项卡中一个接一个地出现.