c# - Come posso aumentare le prestazioni di FlowDocumentScrollViewer?




wpf performance (2)

Eseguo l'esempio che hai fornito, a parte alcuni problemi di threading il problema più grande è la quantità di dati. che rallenta il rendering del testo man mano che cresce.

Ho provato a riscrivere il tuo codice in un modo diverso. Ho utilizzato Tasks, BlockingCollection e Virtualization per migliorare le prestazioni con le ipotesi che l'interesse principale dell'applicazione è la velocità di registrazione.

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

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

Per un esempio completo di lavoro scarica PerformanceTest.zip e verifica se questo è vicino a ciò che ti serve. Ho solo riscritto solo una parte. Se questo esempio sta andando nella direzione desiderata, possiamo implementare il resto della funzionalità. un-comment Task.Delay(1).Wait(); in Bridge.cs se si desidera rallentare la produzione per vedere i registri misti altrimenti la generazione dei registri è troppo veloce, quindi vengono visualizzati uno dopo l'altro nella scheda dei registri.

In una domanda precedente, ho chiesto come ottenere l'output di registrazione in tempo reale in un elemento simile a una casella di testo WPF (il testo dell'appendice di WPF blocca pesantemente il thread dell'interfaccia utente, ma WinForms no? ). La risposta mi ha portato a utilizzare un FlowDocumentScrollViewer , che in effetti era molto più veloce di RichTextBox . Tuttavia, ho scoperto che l'esecuzione di comandi che hanno una grande quantità di output di testo (come 'svn co') porta a un notevole rallentamento nella mia applicazione WPF. Cambiare scheda dopo aver controllato 3 o 4 rami svn molto grandi richiede 3-4 secondi, e sono sicuro che il tempo aumenterebbe con il numero di cassa che faccio. Lo scorrimento ha anche un ritardo notevole.

Come indicato nella domanda che ho linkato sopra, di recente ho passato la mia applicazione da Windows Form a WPF. Mi piace molto il WPF - offre molti vantaggi che non avevo in Forms. Tuttavia, le prestazioni sembrano essere piuttosto un problema in WPF, almeno per me. Nella versione Forms della mia applicazione, potevo stampare enormi quantità di testo sul controllo RichTextBox e non avevo affatto rallentamenti nella mia app. La commutazione delle schede era istantanea e lo scorrimento era continuo. Questa è l'esperienza che voglio nella mia app WPF.

Quindi la mia domanda è questa: come posso migliorare le prestazioni del mio FlowDocumentScrollViewer per abbinare le prestazioni del RichTextBox Windows Form, senza perdere le capacità di formattazione come grassetto e corsivo, e senza perdere la funzionalità di copia / incolla? Sono disposto a cambiare i controlli WPF finché offrono le funzionalità di formattazione che sto cercando.

Ecco il mio codice di stampa, per riferimento:

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 le dimensioni dei caratteri e metto in grassetto il testo quando si stampa testo "importante". La ragione per cui questo codice è così tante righe è perché sto cercando di riutilizzare lo stesso paragrafo per tutto il testo fino a quando non prendo il testo "importante"; Aggiungo un nuovo paragrafo con tutto il testo "importante", quindi aggiungo un altro paragrafo una volta che torno al testo non importante e aggiungo quel paragrafo fino a quando non prendo più testo "importante". Speravo che riutilizzare lo stesso paragrafo avrebbe migliorato le prestazioni.

Inoltre, si noti che sto stampando lo stdout su un FlowDocumentScrollViewer , su stderr su un altro FlowDocumentScrollViewer e contemporaneamente su un terzo FlowDocumentScrollViewer . Quindi ogni riga di stdout e stderr viene stampata tecnicamente due volte, raddoppiando il carico sulla mia app. Ancora una volta, questo non era un problema in WinForms.

Di seguito è riportato un esempio di codice completo, come richiesto nei commenti. È estremamente semplice (3 FlowDocumentScrollViewer e una semplice stampa), ma rallenta ancora più a lungo circa 20000 righe di testo, e molto peggio.

EDIT: il codice di esempio è stato rimosso. Al suo posto è il codice di lavoro per risolvere i miei problemi di prestazioni. FlowDocumentScrollViewer proprio come farebbe FlowDocumentScrollViewer , con una sola eccezione: non puoi selezionare sottostringhe delle linee. Sto cercando di aggiustarlo, anche se sembra difficile.

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

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"
        xmlns:l="clr-namespace:PerformanceTest"
        WindowStartupLocation="CenterScreen">
  <Grid>
    <TabControl>
      <TabControl.Resources>
        <Style TargetType="ListBox">
          <Setter Property="TextElement.FontFamily" Value="Consolas" />
          <Setter Property="TextElement.FontSize" Value="12" />
          <Setter Property="VirtualizingStackPanel.IsVirtualizing" Value="True" />
          <Setter Property="VirtualizingStackPanel.VirtualizationMode" Value="Recycling" />
          <Setter Property="l:ListBoxSelector.Enabled" Value="True" />
          <Setter Property="ContextMenu">
            <Setter.Value>
              <ContextMenu>
                <MenuItem Command="Copy" />
              </ContextMenu>
            </Setter.Value>
          </Setter>
        </Style>
        <Style TargetType="ListBoxItem">
          <Setter Property="HorizontalAlignment" Value="Left" />
          <Setter Property="Margin" Value="0" />
          <Setter Property="Template">
            <Setter.Value>
              <ControlTemplate TargetType="ListBoxItem">
                <TextBlock Text="{Binding Text}" TextWrapping="Wrap"
                    Background="{TemplateBinding Background}"
                    Foreground="{TemplateBinding Foreground}"
                    FontWeight="{TemplateBinding FontWeight}" />
                <ControlTemplate.Triggers>
                  <DataTrigger Binding="{Binding IsImportant}" Value="true">
                    <Setter Property="TextElement.FontWeight" Value="SemiBold" />
                    <Setter Property="TextElement.FontSize" Value="14" />
                  </DataTrigger>
                  <Trigger Property="IsSelected" Value="true">
                    <Setter Property="Background" Value="{StaticResource {x:Static SystemColors.HighlightBrushKey}}" />
                    <Setter Property="Foreground" Value="{StaticResource {x:Static SystemColors.HighlightTextBrushKey}}" />
                  </Trigger>
                </ControlTemplate.Triggers>
              </ControlTemplate>
            </Setter.Value>
          </Setter>
        </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>
          <!--<RichTextBox x:Name="OutputBox" ScrollViewer.VerticalScrollBarVisibility="Auto"/>-->
          <ListBox Grid.Column="0" ItemsSource="{Binding Output,RelativeSource={RelativeSource FindAncestor,AncestorType=Window}}"
                   ScrollViewer.HorizontalScrollBarVisibility="Disabled">
            <ListBox.CommandBindings>
              <CommandBinding Command="Copy" Executed="CopyExecuted" />
            </ListBox.CommandBindings>
          </ListBox>

          <GridSplitter Grid.Column="1" Width="5" ResizeBehavior="PreviousAndNext" />
          <ListBox Grid.Column="2" ItemsSource="{Binding Errors,RelativeSource={RelativeSource FindAncestor,AncestorType=Window}}" 
                   ScrollViewer.HorizontalScrollBarVisibility="Disabled">
            <ListBox.CommandBindings>
              <CommandBinding Command="Copy" Executed="CopyExecuted" />
            </ListBox.CommandBindings>
          </ListBox>
        </Grid>

      </TabItem>
      <TabItem Header="Log">
        <Grid>
          <ListBox Grid.Column="0" ItemsSource="{Binding Logs,RelativeSource={RelativeSource FindAncestor,AncestorType=Window}}" 
                   ScrollViewer.HorizontalScrollBarVisibility="Disabled">
            <ListBox.CommandBindings>
              <CommandBinding Command="Copy" Executed="CopyExecuted" />
            </ListBox.CommandBindings>
          </ListBox>
        </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;
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);
        }
    }
}

ListBoxSelector.cs è nella risposta di @ pushpraj.






text