How do I increase performance of FlowDocumentScrollViewer?

前端 未结 2 711
野性不改
野性不改 2020-12-31 11:57

In a previous question, I asked how to get real-time logging output in a WPF textbox-like element (WPF append text blocks UI thread heavily but WinForms doesn't?). The a

相关标签:
2条回答
  • 2020-12-31 12:24

    I execute the sample you've provided, apart from few threading issues the biggest issue is amount of data. which slows down the text rendering as the it grows.

    I tried to rewrite your code in a different way. I used Tasks, BlockingCollection and Virtualization to improve the performance with assumptions that main interest of the application is logging speed.

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

    For a full working sample download PerformanceTest.zip and see if this is close to what you need. I have only rewritten only some portion. If this example is heading to the desired direction, we can implement rest of the functionality. un-comment Task.Delay(1).Wait(); in Bridge.cs if you may want to slow down the production to see the mixed logs otherwise the logs generation is too fast so it appear one after other in logs tab.

    0 讨论(0)
  • 2020-12-31 12:27

    Using FlowDocumentPageViewer will help the performance since it loads document asynchronously.

    0 讨论(0)
提交回复
热议问题