2014-04-30 2 views
0

У меня есть простой список элементов в моем приложении WPF, который я пытаюсь выжать из него. В приведенном ниже коде я добавляю миллион записей и сигнализирую об изменении в моем списке элементов.Как улучшить производительность добавления диапазона в ListView

У меня есть следующий код XAML:

<Window x:Class="Log_.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     Title="Log+ Viewer" Height="400" Width="500"> 
    <Grid Name="MainGrid"> 
     <Grid.RowDefinitions> 
      <RowDefinition Height="*"/> 
     </Grid.RowDefinitions> 
     <Grid.ColumnDefinitions> 
      <ColumnDefinition Width="*"/> 
     </Grid.ColumnDefinitions> 
     <TabControl> 
      <TabItem Header="Everything"> 
       <Grid> 
        <Grid.RowDefinitions> 
         <RowDefinition Height="*"/> 
        </Grid.RowDefinitions> 
        <Grid.ColumnDefinitions> 
         <ColumnDefinition Width="*"/> 
        </Grid.ColumnDefinitions> 
        <ListView ItemsSource="{Binding LogRecords}"> 
         <ListView.View> 
          <GridView> 
           <GridViewColumn Header="Message" DisplayMemberBinding="{Binding Message}"/> 
           <GridViewColumn Header="Timestamp" DisplayMemberBinding="{Binding Timestamp}"/> 
          </GridView> 
         </ListView.View> 
        </ListView> 
       </Grid> 
      </TabItem> 
     </TabControl> 
    </Grid> 
</Window> 

Здесь является C# код:

using System; 
using System.Collections.Generic; 
using System.Collections.ObjectModel; 
using System.Collections.Specialized; 
using System.Diagnostics; 
using System.Linq; 
using System.Text; 
using System.Threading; 
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; 

namespace Log_ 
{ 
    /// <summary> 
    /// Interaction logic for MainWindow.xaml 
    /// </summary> 
    public partial class MainWindow : Window 
    { 

     public ObservableList<LogRecord> LogRecords { get; set; } 

     public MainWindow() 
     { 
      InitializeComponent(); 
      LogRecords = new ObservableList<LogRecord>(); 
      DataContext = this; 
      new Thread(() => 
      { 
       LogRecord record = new LogRecord(); 
       record.Message = "Hello, world."; 
       record.Timestamp = DateTime.Now; 
       List<LogRecord> logRecordList = new List<LogRecord>(); 
       for (int i = 0; i < 1000000; i++) 
       { 
        logRecordList.Add(record); 
       } 
       Stopwatch timer = new Stopwatch(); 
       timer.Start(); 
       Dispatcher.Invoke(() => 
       { 
        LogRecords.AddRange(logRecordList); 
       }); 
       timer.Stop(); 
       Console.WriteLine("The operation took {0} milliseconds.", timer.ElapsedMilliseconds); 
      }).Start(); 
     } 

     public class LogRecord 
     { 
      public string Message { get; set; } 
      public DateTime Timestamp { get; set; } 
     } 

     public class ObservableList<T> : IEnumerable<T>, INotifyCollectionChanged 
     { 

      public List<T> UnderlyingList = new List<T>(); 

      public event NotifyCollectionChangedEventHandler CollectionChanged; 

      public void AddRange(IEnumerable<T> list) 
      { 
       UnderlyingList.AddRange(list); 
       OnCollectionChange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, UnderlyingList)); 
      } 

      protected virtual void OnCollectionChange(NotifyCollectionChangedEventArgs e) 
      { 
       if (CollectionChanged != null) 
       { 
        CollectionChanged(this, e); 
       } 
      } 

      public IEnumerator<T> GetEnumerator() 
      { 
       return UnderlyingList.GetEnumerator(); 
      } 

      System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
      { 
       return UnderlyingList.GetEnumerator(); 
      } 
     } 
    } 
} 

Выход: "Операция прошла 4834 миллисекунды."

Это кажется абсурдным количеством времени для его добавления этих записей в виде ряда записей. Я нарушаю виртуализацию пользовательского интерфейса здесь, потому что источник моих объектов наследует IEnumerable, как это, или это нормальная производительность? Как я могу заставить этот код работать намного быстрее, чем в настоящее время?

ответ

0

Моя проблема заключалась в том, что, на самом деле, я думаю, наследуя перечислимое была очень плохая идея, в то время как я должен был пойти только с ObservableCollection ... рассмотреть разницу в производительности:

using System; 
using System.Collections.Generic; 
using System.Collections.ObjectModel; 
using System.Collections.Specialized; 
using System.Diagnostics; 
using System.Linq; 
using System.Text; 
using System.Threading; 
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; 

namespace Log_ 
{ 
    /// <summary> 
    /// Interaction logic for MainWindow.xaml 
    /// </summary> 
    public partial class MainWindow : Window 
    { 

     public ObservableList<LogRecord> LogRecords { get; set; } 

     public MainWindow() 
     { 
      InitializeComponent(); 
      LogRecords = new ObservableList<LogRecord>(); 
      DataContext = this; 
      new Thread(() => 
      { 
       LogRecord record = new LogRecord(); 
       record.Message = "Hello, world."; 
       record.Timestamp = DateTime.Now; 
       List<LogRecord> logRecordList = new List<LogRecord>(); 
       for (int i = 0; i < 1000000; i++) 
       { 
        logRecordList.Add(record); 
       } 
       Stopwatch timer = new Stopwatch(); 
       timer.Start(); 
       Dispatcher.Invoke(() => 
       { 
        LogRecords.AddRange(logRecordList); 
       }); 
       timer.Stop(); 
       Console.WriteLine("The operation took {0} milliseconds.", timer.ElapsedMilliseconds); 
      }).Start(); 
     } 

     public class LogRecord 
     { 
      public string Message { get; set; } 
      public DateTime Timestamp { get; set; } 
     } 

     public class ObservableList<T> : ObservableCollection<T> 
     { 

      public override event NotifyCollectionChangedEventHandler CollectionChanged; 

      public void AddRange(IEnumerable<T> list) 
      { 
       foreach (var item in list) 
       { 
        Items.Add(item); 
       } 
       OnCollectionChange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); 
      } 

      protected virtual void OnCollectionChange(NotifyCollectionChangedEventArgs e) 
      { 
       if (CollectionChanged != null) 
       { 
        CollectionChanged(this, e); 
       } 
      } 
     } 
    } 
} 

Операция длилась 75 миллисекунды.

0

Если у вас есть миллион записей, вы, вероятно, не хотите обновлять интерфейс со всеми этими записями. Рассмотрите возможность использования подкачки или загрузки записей в виде сетки, когда пользователь прокручивает список.

+0

Но не в ListView включить виртуализацию пользовательского интерфейса, так что он отображает только записи он должен отображаться по умолчанию, и из-за этого, если он не справится с этим поведением уже, особенно учитывая тот факт, что я только сигнализирую, что он обновляется один раз из-за логики в ObservableList ... если, конечно, не наследует IEnumerable заставляет его сигнализировать несколько раз ...? – Alexandru

+0

Вы включили отложенную прокрутку? ScrollViewer.IsDeferredScrollingEnabled = "True" – jake

+0

Джейк, я нашел решение ... Я думаю, что наследование IEnumerable действительно было плохой идеей ... Я отправлю его сейчас. Пожалуйста, проверьте это, но обратите внимание, что это не имеет ничего общего с ScrollViewer с включенной отсроченной прокруткой. – Alexandru

0

Одна вещь, вы можете попробовать, вероятно, использует локальную переменную для хранения и последнего назначения его на LogRecords собственности ..

public MainWindow() 
{ 
    InitializeComponent(); 

    //A local variable 
    var logRecords = new ObservableList<LogRecord>(); 
    DataContext = this; 
    new Thread(() => 
    { 
     LogRecord record = new LogRecord(); 
     record.Message = "Hello, world."; 
     record.Timestamp = DateTime.Now; 
     List<LogRecord> logRecordList = new List<LogRecord>(); 
     for (int i = 0; i < 1000000; i++) 
     { 
      logRecordList.Add(record); 
     } 
     Stopwatch timer = new Stopwatch(); 
     timer.Start(); 
     Dispatcher.Invoke(() => 
     { 
      // Should prevent UI to update itself 
      logRecords .AddRange(logRecordList); 
     }); 
     timer.Stop(); 

     // Assign to actual collection causing UI update 
     LogRecords = logRecords ;  
     Console.WriteLine("The operation took {0} milliseconds.", timer.ElapsedMilliseconds); 

    }).Start(); 
} 
+0

Это уже то, что AddRange для/does. – Alexandru

+0

Да. AddRange должен работать с локальной переменной, а не с фактической коллекцией. – bit

Смежные вопросы