¿Cómo puedo boost el rendimiento de FlowDocumentScrollViewer?

En una pregunta anterior, pregunté cómo obtener la salida del registro en tiempo real en un elemento similar a un cuadro de texto de WPF ( WPF agrega el subproceso de la IU de bloques de texto pero WinForms no? ). La respuesta me llevó a usar un FlowDocumentScrollViewer , que de hecho era mucho más rápido que RichTextBox . Sin embargo, he encontrado que ejecutar comandos que tienen una gran cantidad de salida de texto (como ‘svn co’) conduce a una desaceleración notable en mi aplicación WPF. El cambio de tabs después de verificar 3 o 4 twigs svn muy grandes toma de 3 a 4 segundos, y estoy seguro de que el tiempo boostía con la cantidad de cajas que realizo. El desplazamiento también tiene un notable retraso.

Como se indica en la pregunta que vinculé anteriormente, cambié mi aplicación de Windows Forms a WPF recientemente. Me gusta mucho WPF, me da muchas ventajas que no tenía en Forms. Sin embargo, el rendimiento parece ser un gran problema en WPF, al menos para mí. En la versión de Formularios de mi aplicación, pude imprimir grandes cantidades de texto al control RichTextBox y no tuve ninguna desaceleración en mi aplicación. El cambio de tabs fue instantáneo y el desplazamiento fue ininterrumpido. Esta es la experiencia que quiero en mi aplicación WPF.

Entonces, mi pregunta es: ¿cómo puedo mejorar el rendimiento de mi FlowDocumentScrollViewer para que coincida con el rendimiento de Windows Forms RichTextBox , sin perder capacidades de formato como negrita y cursiva, y sin perder la función de copiar / pegar? Estoy dispuesto a cambiar los controles WPF siempre que ofrezcan las capacidades de formato que estoy buscando.

Aquí está mi código de impresión, para referencia:

 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; } 

Cambio el tamaño de fuente y hago el texto en negrita al imprimir texto “importante”. La razón por la que este código es tantas líneas es porque estoy tratando de reutilizar el mismo párrafo para todo el texto hasta que golpeo el texto “importante”; Agrego un nuevo párrafo con todo el texto “importante”, luego agrego otro párrafo una vez que vuelvo al texto que no es importante y agrego a ese párrafo hasta que golpeo el texto más “importante”. Esperaba que reutilizar el mismo párrafo mejoraría el rendimiento.

Además, debe tenerse en cuenta que estoy imprimiendo stdout en un FlowDocumentScrollViewer , stderr en otro FlowDocumentScrollViewer , y ambos a la vez en un tercer FlowDocumentScrollViewer . Así que cada línea de stdout y stderr se imprime técnicamente dos veces, duplicando la carga en mi aplicación. Una vez más, esto no fue un problema en WinForms.


A continuación se muestra un ejemplo de código completo, como se solicita en los comentarios. Es extremadamente simple (3 FlowDocumentScrollViewer e impresión simple) pero aún ralentiza mucho alrededor de 20000 líneas de texto, y mucho peor después de eso.

EDITAR: El ejemplo de código ha sido eliminado. En su lugar está el código de trabajo para resolver mis problemas de rendimiento. Funciona igual que FlowDocumentScrollViewer , con una excepción: no puede seleccionar subcadenas de líneas. Estoy buscando arreglar eso, aunque parezca difícil.

Puente.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 está en la respuesta de @ pushpraj.

Ejecuto la muestra que ha proporcionado, aparte de algunos problemas de subprocesos, el mayor problema es la cantidad de datos. lo que ralentiza la representación del texto a medida que crece.

Intenté reescribir tu código de una manera diferente. Utilicé Tasks, BlockingCollection y Virtualization para mejorar el rendimiento con los supuestos de que el interés principal de la aplicación es la velocidad de registro.

Puente.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; } } } 

Para una muestra de trabajo completa, descargue PerformanceTest.zip y vea si esto está cerca de lo que necesita. Solo he reescrito solo una parte. Si este ejemplo se dirige a la dirección deseada, podemos implementar el rest de la funcionalidad. un-comment Task.Delay(1).Wait(); en Bridge.cs si es posible que desee ralentizar la producción para ver los registros mixtos, de lo contrario, la generación de registros es demasiado rápida para que aparezcan una tras otra en la pestaña de registros.

El uso de FlowDocumentPageViewer ayudará al rendimiento, ya que carga el documento de forma asíncrona.