<?xml version='1.0' encoding="utf-8"?>
      <rss version='2.0'>
      <channel>
      <title>Форум на Исходниках.RU</title>
      <link>https://forum.sources.ru</link>
      <description>Форум на Исходниках.RU</description>
      <generator>Форум на Исходниках.RU</generator>
  	
      <item>
        <guid isPermaLink='true'>https://forum.sources.ru/index.php?showtopic=377354&amp;view=findpost&amp;p=3316951</guid>
        <pubDate>Mon, 27 May 2013 11:30:34 +0000</pubDate>
        <title>Утечки памяти при программировании на WPF</title>
        <link>https://forum.sources.ru/index.php?showtopic=377354&amp;view=findpost&amp;p=3316951</link>
        <description><![CDATA[Craft: В этой статьи озвучена тема утечек памяти при программировании на WPF. Информация для материала взята с источников, приведенных в списке литературы. Это было сделано для того, чтобы информация об утечках памяти при проектировании и программировании на WPF оставалась актуальной, так как ссылки в интернете часто теряются. Пожалуй, начнем из самых популярных утечек, которые разработчики часто совершают при разработке программного обеспечения (ПО).  <br>
<br>
<span class='tag-size' data-value='14' style='font-size:14pt;'>Unregistered Events</span><br>
Опишем гипотетический пример, в котором проявляется утечка памяти,  если не отписаться от события в родительском элементе.<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">public void AddBook()</div><div class="code_line">{</div><div class="code_line">&nbsp;&nbsp; &nbsp;var book = new LibraryBook</div><div class="code_line">&nbsp;&nbsp; &nbsp;{</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;Author = &quot;Jon Skeet&quot;,</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;Title = &quot;C# in Depth&quot;,</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;Count = 3,</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;SN = &quot;ISBN: 9781617291340&quot;,</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;Year = new DateTime(2013, 9, 10)</div><div class="code_line">&nbsp;&nbsp; &nbsp;};</div><div class="code_line">&nbsp;&nbsp; &nbsp;_books.Add(book);</div><div class="code_line">&nbsp;</div><div class="code_line">&nbsp;&nbsp; &nbsp;book.RemoveBook += OnRemoveBook;</div><div class="code_line">}</div><div class="code_line">&nbsp;</div><div class="code_line">public void OnRemoveBook(LibraryBook book)</div><div class="code_line">{</div><div class="code_line">&nbsp;&nbsp; &nbsp;_books.Remove(book);</div><div class="code_line">}</div></ol></div></div></div></div><script>preloadCodeButtons('1');</script><br>
В данном примере ошибка заключается в том, что событие RemoveBook содержит ссылку на объект LibraryBook, и даже после того, как мы удалили объект из коллекции, он все еще продолжает жить. Вот как будет выглядеть исправление данной утечки:<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">public void OnRemoveBook(LibraryBook book)</div><div class="code_line">{</div><div class="code_line">&nbsp;&nbsp; &nbsp;_books.Remove(book);</div><div class="code_line">&nbsp;&nbsp; &nbsp;book.RemoveBook -= OnRemoveBook;</div><div class="code_line">}</div></ol></div></div></div></div><br>
После этого GC подберёт весь мусор и очистит память, которую занимал данный объект.<br>
Данная утечка памяти имеет место при разработке программ на управляемых языках. Эта проблема относится не только к WPF и Silverlight, а также к программам, написанным на VB.NET, Windows Forms, Console Application, а также имеет место везде, где используется модель событий .Net.  <br>
<br>
<span class='tag-size' data-value='14' style='font-size:14pt;'>Databinding</span><br>
Утечка памяти может произойти, если имеем дочерний объект, который связывается через байндинг со свойством родителя.<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">&#60;Grid Name=&quot;mainGrid&quot;&#62;</div><div class="code_line">&nbsp;&nbsp; &nbsp;&#60;TextBlock Name=&quot;txtMainText&quot; Text=&quot;{Binding ElementName=mainGrid, Path=Children.Count}&quot; /&#62;</div><div class="code_line">&#60;/Grid&#62;</div></ol></div></div></div></div><br>
Условие будет выполняться только тогда, когда связанное свойство является свойством PropertyDescriptor, каким и является Children.Count. Это потому, что с целью определения изменений свойств, происходящих в PropertyDescriptor, фреймфорк должен подписаться на событие ValueChanged, что, в свою очередь, создаёт сильную цепную зависимость. Эту проблему можно решить несколькими способами:<ul class="tag-list"><li>Добавить необходимое DependencyProperty, которое будет просто возвращать значение, необходимое свойству PropertyDescriptor.</li><li>Установить свойство Mode для байндинга в OneTime.</li><li>Предоставить интерфейс INotifyProipertyChanged для объекта (ссылка).</li><li>Добавить очистку байндинга после выхода со страници (OnClose). BindingOperations.ClearBinding(txtMainText, TextBlock.TextProperty);</li></ul><br>
<span class='tag-size' data-value='14' style='font-size:14pt;'>Статические события (Static event)</span><br>
Подписка на событие в статическом классе создает сильное связывание на любые объекты, которые обрабатывают это событие. Использование подписок на статические события часто может приводить к утечке ресурсов. Строгие ссылки препятствуют сборке мусора, и это является основной причиной того, что Ваши объекты не будут уничтожены. Чтобы  уничтожить ссылку на статический класс, достаточно просто отписаться от события, например, при закрытии окна и т.д. Одним из способов борьбы с утечкой памяти при использовании статических событий является использование шаблона слабых событий (weak event patterns). Об этом можно прочитать в статье <a class='tag-url' href='http://sonyks2007.blogspot.com/2013/11/c_6109.html' target='_blank'>Слабые события в C#</a>. <br>
<br>
<span class='tag-size' data-value='14' style='font-size:14pt;'>Command Binding</span><br>
Command binding - очень полезная возможность, которую нам предоставляет WPF. В данном примере речь пойдет о классе CommonBinding, который позволяет использовать в Ваших приложениях общие команды, доступные для всего приложения (common application command) такие как Cut, Copy, Undo и т.д. Данный подход приведет к утечке памяти, если в родительском классе будет содержаться ссылка на дочерний объект.<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">CommandBinding cutCmdBinding = new CommandBinding(ApplicationCommands.Cut, OnMyCutHandler, OnCanICut);</div><div class="code_line">&nbsp;</div><div class="code_line">mainWindow.main.CommandBindings.Add(cutCmdBinding);</div><div class="code_line">&nbsp;</div><div class="code_line">&nbsp;</div><div class="code_line">…..</div><div class="code_line">void OnMyCutHandler (object target, ExecutedRoutedEventArgs e)</div><div class="code_line">{</div><div class="code_line">&nbsp;</div><div class="code_line">&nbsp;MessageBox.Show(&quot;You attempted to CUT&quot;);</div><div class="code_line">}</div><div class="code_line">&nbsp;</div><div class="code_line">&nbsp;</div><div class="code_line">void OnCanICut (object sender, CanExecuteRoutedEventArgs e)</div><div class="code_line">{</div><div class="code_line">&nbsp;e.CanExecute = true;</div><div class="code_line">}</div></ol></div></div></div></div><br>
В данном случае утечка происходит в коде mainWindow.main.CommandBindings; мы имеем строгую ссылку на дочерний объект. В результате события, когда дочерний объект (child) закрывается, объект все еще будет оставаться в памяти в связи с тем, что на него ссылается родительский объект. Решение этой проблемы очень простое: после закрытия дочернего окна (элемента) нужно удалить ссылку на него в родителя.<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">mainWindow.main.CommandBindings.Remove(cutCmdBinding);</div></ol></div></div></div></div><br>
<br>
<span class='tag-size' data-value='14' style='font-size:14pt;'>DispatcherTimer</span><br>
Неправильное использование объекта DispatcherTimer также может вызвать утечку памяти. Приведенный ниже код создает новый DispatcherTimer в элементе управления пользователя (UserControl), и, чтобы легче увидеть утечки, в код также добавлен массив байт, который называется myMemory.<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">public byte[] myMemory = new byte[50 * 1024 * 1024];</div><div class="code_line">&nbsp;</div><div class="code_line">System.Windows.Threading.DispatcherTimer _timer = new System.Windows.Threading.DispatcherTimer();</div><div class="code_line">int count = 0;</div><div class="code_line">&nbsp;</div><div class="code_line">&nbsp;</div><div class="code_line">private void MyLabel_Loaded(object sender, RoutedEventArgs e)</div><div class="code_line">{</div><div class="code_line">&nbsp;</div><div class="code_line">&nbsp;&nbsp; &nbsp;_timer.Interval = TimeSpan.FromMilliseconds(1000);</div><div class="code_line">&nbsp;</div><div class="code_line">&nbsp;&nbsp; &nbsp;_timer.Tick += new EventHandler(delegate(object s, EventArgs ev)</div><div class="code_line">&nbsp;&nbsp; &nbsp;{</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;count++;</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;textBox1.Text = count.ToString();</div><div class="code_line">&nbsp;&nbsp; &nbsp;});</div><div class="code_line">&nbsp;</div><div class="code_line">&nbsp;&nbsp; &nbsp;_timer.Start();</div><div class="code_line">}</div></ol></div></div></div></div><br>
В данном примере утечки памяти можно добиться таким способом: добавить дочерние UserControl, которые используют объект класса DispatcherTimer, например, в StackPanel. Затем с помощью кнопки запрограммировать удаление данных контролов  со StackPanel. Когда Вы запустите, например, ANTS профайлер, то сможете увидеть утечку. Проблема данного подхода в том, что класс Dispatcher, который используется для предоставления доступа к главному потоку формы, удерживает объекты DispatcherTimers, которые не уничтожены. Чтобы избежать данной утечки памяти, необходимо просто остановить таймер и обнулить его.<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">_timer.Stop();</div><div class="code_line">_timer = null;</div></ol></div></div></div></div><br>
<br>
<span class='tag-size' data-value='14' style='font-size:14pt;'>Использование BitmapImage в Image Source</span><br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">bi1 = &nbsp; &nbsp; //bi1 is static</div><div class="code_line">&nbsp;&nbsp; &nbsp;new BitmapImage(new Uri(&quot;Bitmap1.bmp&quot;, UriKind.RelativeOrAbsolute));</div><div class="code_line">//bi1.Freeze() //if you do not Freeze, your app will leak memory</div><div class="code_line">m_Image1 = new Image();</div><div class="code_line">m_Image1.Source = bi1;</div><div class="code_line">MyStackPanel.Children.Add(m_Image1);</div></ol></div></div></div></div><br>
Эта утечка срабатывает, потому что WPF держит строгую ссылку между статической переменной  BitmapImage (bi1) и Image (m_Image1). BitmapImage (bi1) объявляется как статическая переменная, поэтому она не подбирается сборщиком мусора, когда мы закрываем окно, которое использует данный код. Эта утечка может произойти только при использовании BitmapImage. Она не появляется при использовании, например, DrawingImage. Обходной путь зависит от используемого сценария. Один из способов - заморозить BitmapImage через метод Freeze. WPF не перехватывает события для объектов, которые заморожены. В общем, нужно заморозить объекты, когда это возможно, чтобы улучшить производительность приложения. Дополнительно можно посмотреть <a class='tag-url' href='http://msdn.microsoft.com/en-us/library/ms750509.aspx' target='_blank'>здесь</a>. С использованием BitmapImage связано еще несколько нюансов, с которыми можно ознакомиться по данной ссылке: <a class='tag-url' href='http://blogs.msdn.com/b/jgoldb/archive/2008/02/04/finding-memory-leaks-in-wpf-based-applications.aspx' target='_blank'>WPF Performance and .NET Framework Client Profile</a>.<br>
<br>
<span class='tag-size' data-value='14' style='font-size:14pt;'>Итоги</span><br>
В данной статье рассмотрены популярные утечки памяти при программировании на WPF, а также способы их решения. Надеюсь, статья получилась не очень скучной и поможет Вам избежать утечек памяти в дальнейшем. <br>
<br>
<span class='tag-size' data-value='14' style='font-size:14pt;'>Источники:</span><br>
<a class='tag-url' href='http://sonyks2007.blogspot.com/2013/12/wpf.html' target='_blank'>Оригинал статьи</a><br>
<a class='tag-url' href='http://ozcode-orchard.azurewebsites.net/top-3-memory-leak-inducing-pitfalls-of-wpf-programming' target='_blank'>Топ 3 популярных утечек памяти на WPF</a> - В статье рассказывается о самых распространенных ошибках при программировании на WPF.<br>
<a class='tag-url' href='http://www.red-gate.com/products/dotnet-development/ants-memory-profiler/learning-memory-management/WPF-silverlight-pitfalls' target='_blank'>Top 5 Memory leaks in WPF and Silverlight</a><br>
<a class='tag-url' href='http://blogs.msdn.com/b/jgoldb/archive/2008/02/04/finding-memory-leaks-in-wpf-based-applications.aspx' target='_blank'>WPF Performance and .NET Framework Client Profile</a><br>
<a class='tag-url' href='http://msdn.microsoft.com/en-us/library/ms750509.aspx' target='_blank'>Freezable Objects Overview</a>]]></description>
        <author>Craft</author>
        <category>.NET FAQ</category>
      </item>
	
      </channel>
      </rss>
	