На главную
ПРАВИЛА FAQ Помощь Участники Календарь Избранное DigiMania RSS
msm.ru
Модераторы: maxim84_, juice
  
> Утечки памяти при программировании на WPF
    В этой статьи озвучена тема утечек памяти при программировании на WPF. Информация для материала взята с источников, приведенных в списке литературы. Это было сделано для того, чтобы информация об утечках памяти при проектировании и программировании на WPF оставалась актуальной, так как ссылки в интернете часто теряются. Пожалуй, начнем из самых популярных утечек, которые разработчики часто совершают при разработке программного обеспечения (ПО).

    Unregistered Events
    Опишем гипотетический пример, в котором проявляется утечка памяти, если не отписаться от события в родительском элементе.
    ExpandedWrap disabled
      public void AddBook()
      {
          var book = new LibraryBook
          {
              Author = "Jon Skeet",
              Title = "C# in Depth",
              Count = 3,
              SN = "ISBN: 9781617291340",
              Year = new DateTime(2013, 9, 10)
          };
          _books.Add(book);
       
          book.RemoveBook += OnRemoveBook;
      }
       
      public void OnRemoveBook(LibraryBook book)
      {
          _books.Remove(book);
      }

    В данном примере ошибка заключается в том, что событие RemoveBook содержит ссылку на объект LibraryBook, и даже после того, как мы удалили объект из коллекции, он все еще продолжает жить. Вот как будет выглядеть исправление данной утечки:
    ExpandedWrap disabled
      public void OnRemoveBook(LibraryBook book)
      {
          _books.Remove(book);
          book.RemoveBook -= OnRemoveBook;
      }

    После этого GC подберёт весь мусор и очистит память, которую занимал данный объект.
    Данная утечка памяти имеет место при разработке программ на управляемых языках. Эта проблема относится не только к WPF и Silverlight, а также к программам, написанным на VB.NET, Windows Forms, Console Application, а также имеет место везде, где используется модель событий .Net.

    Databinding
    Утечка памяти может произойти, если имеем дочерний объект, который связывается через байндинг со свойством родителя.
    ExpandedWrap disabled
      <Grid Name="mainGrid">
          <TextBlock Name="txtMainText" Text="{Binding ElementName=mainGrid, Path=Children.Count}" />
      </Grid>

    Условие будет выполняться только тогда, когда связанное свойство является свойством PropertyDescriptor, каким и является Children.Count. Это потому, что с целью определения изменений свойств, происходящих в PropertyDescriptor, фреймфорк должен подписаться на событие ValueChanged, что, в свою очередь, создаёт сильную цепную зависимость. Эту проблему можно решить несколькими способами:
    • Добавить необходимое DependencyProperty, которое будет просто возвращать значение, необходимое свойству PropertyDescriptor.
    • Установить свойство Mode для байндинга в OneTime.
    • Предоставить интерфейс INotifyProipertyChanged для объекта (ссылка).
    • Добавить очистку байндинга после выхода со страници (OnClose). BindingOperations.ClearBinding(txtMainText, TextBlock.TextProperty);

    Статические события (Static event)
    Подписка на событие в статическом классе создает сильное связывание на любые объекты, которые обрабатывают это событие. Использование подписок на статические события часто может приводить к утечке ресурсов. Строгие ссылки препятствуют сборке мусора, и это является основной причиной того, что Ваши объекты не будут уничтожены. Чтобы уничтожить ссылку на статический класс, достаточно просто отписаться от события, например, при закрытии окна и т.д. Одним из способов борьбы с утечкой памяти при использовании статических событий является использование шаблона слабых событий (weak event patterns). Об этом можно прочитать в статье Слабые события в C#.

    Command Binding
    Command binding - очень полезная возможность, которую нам предоставляет WPF. В данном примере речь пойдет о классе CommonBinding, который позволяет использовать в Ваших приложениях общие команды, доступные для всего приложения (common application command) такие как Cut, Copy, Undo и т.д. Данный подход приведет к утечке памяти, если в родительском классе будет содержаться ссылка на дочерний объект.
    ExpandedWrap disabled
      CommandBinding cutCmdBinding = new CommandBinding(ApplicationCommands.Cut, OnMyCutHandler, OnCanICut);
       
      mainWindow.main.CommandBindings.Add(cutCmdBinding);
       
       
      …..
      void OnMyCutHandler (object target, ExecutedRoutedEventArgs e)
      {
       
       MessageBox.Show("You attempted to CUT");
      }
       
       
      void OnCanICut (object sender, CanExecuteRoutedEventArgs e)
      {
       e.CanExecute = true;
      }

    В данном случае утечка происходит в коде mainWindow.main.CommandBindings; мы имеем строгую ссылку на дочерний объект. В результате события, когда дочерний объект (child) закрывается, объект все еще будет оставаться в памяти в связи с тем, что на него ссылается родительский объект. Решение этой проблемы очень простое: после закрытия дочернего окна (элемента) нужно удалить ссылку на него в родителя.
    ExpandedWrap disabled
      mainWindow.main.CommandBindings.Remove(cutCmdBinding);


    DispatcherTimer
    Неправильное использование объекта DispatcherTimer также может вызвать утечку памяти. Приведенный ниже код создает новый DispatcherTimer в элементе управления пользователя (UserControl), и, чтобы легче увидеть утечки, в код также добавлен массив байт, который называется myMemory.
    ExpandedWrap disabled
      public byte[] myMemory = new byte[50 * 1024 * 1024];
       
      System.Windows.Threading.DispatcherTimer _timer = new System.Windows.Threading.DispatcherTimer();
      int count = 0;
       
       
      private void MyLabel_Loaded(object sender, RoutedEventArgs e)
      {
       
          _timer.Interval = TimeSpan.FromMilliseconds(1000);
       
          _timer.Tick += new EventHandler(delegate(object s, EventArgs ev)
          {
              count++;
              textBox1.Text = count.ToString();
          });
       
          _timer.Start();
      }

    В данном примере утечки памяти можно добиться таким способом: добавить дочерние UserControl, которые используют объект класса DispatcherTimer, например, в StackPanel. Затем с помощью кнопки запрограммировать удаление данных контролов со StackPanel. Когда Вы запустите, например, ANTS профайлер, то сможете увидеть утечку. Проблема данного подхода в том, что класс Dispatcher, который используется для предоставления доступа к главному потоку формы, удерживает объекты DispatcherTimers, которые не уничтожены. Чтобы избежать данной утечки памяти, необходимо просто остановить таймер и обнулить его.
    ExpandedWrap disabled
      _timer.Stop();
      _timer = null;


    Использование BitmapImage в Image Source
    ExpandedWrap disabled
      bi1 =     //bi1 is static
          new BitmapImage(new Uri("Bitmap1.bmp", UriKind.RelativeOrAbsolute));
      //bi1.Freeze() //if you do not Freeze, your app will leak memory
      m_Image1 = new Image();
      m_Image1.Source = bi1;
      MyStackPanel.Children.Add(m_Image1);

    Эта утечка срабатывает, потому что WPF держит строгую ссылку между статической переменной BitmapImage (bi1) и Image (m_Image1). BitmapImage (bi1) объявляется как статическая переменная, поэтому она не подбирается сборщиком мусора, когда мы закрываем окно, которое использует данный код. Эта утечка может произойти только при использовании BitmapImage. Она не появляется при использовании, например, DrawingImage. Обходной путь зависит от используемого сценария. Один из способов - заморозить BitmapImage через метод Freeze. WPF не перехватывает события для объектов, которые заморожены. В общем, нужно заморозить объекты, когда это возможно, чтобы улучшить производительность приложения. Дополнительно можно посмотреть здесь. С использованием BitmapImage связано еще несколько нюансов, с которыми можно ознакомиться по данной ссылке: WPF Performance and .NET Framework Client Profile.

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

    Источники:
    Оригинал статьи
    Топ 3 популярных утечек памяти на WPF - В статье рассказывается о самых распространенных ошибках при программировании на WPF.
    Top 5 Memory leaks in WPF and Silverlight
    WPF Performance and .NET Framework Client Profile
    Freezable Objects Overview
    Сообщение отредактировано: Craft -
    0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
    0 пользователей:


    Рейтинг@Mail.ru
    [ Script Execution time: 0,0933 ]   [ 17 queries used ]   [ Generated: 21.11.17, 16:04 GMT ]