#c_sharp #net
По рекомендации Qwertiy из прошлой темы, выделяю сей вопрос в отдельную. Собственно проблема в указана в заголовке. Есть код и при его работе постепенно увеличивается отъедаемый приложением объем оперативной памяти. Чем больше то изображение, с которым работает код, тем заметней эта утечка. Как исправить ситуацию? using System; using System.Drawing; using System.Drawing.Drawing2D; using System.Windows.Forms; namespace ImgRotate { public partial class Form1 : Form { Bitmap btmpIcn; public Form1() { InitializeComponent(); timer1.Enabled = true; btmpIcn = notifyIcon1.Icon.ToBitmap(); } int an; private void timer1_Tick(object sender, EventArgs e) { if (an >= 350) an = 0; else an += 10; notifyIcon1.Icon = Icon.FromHandle(RotateImage(btmpIcn, an).GetHicon()); } ////// method to rotate an image either clockwise or counter-clockwise /// /// the image to be rotated /// the angle (in degrees). /// NOTE: /// Positive values will rotate clockwise /// negative values will rotate counter-clockwise /// ///public static Bitmap RotateImage(Image img, float rotationAngle) { //create an empty Bitmap image Bitmap bmp = new Bitmap(img.Width, img.Height); //turn the Bitmap into a Graphics object Graphics gfx = Graphics.FromImage(bmp); //now we set the rotation point to the center of our image gfx.TranslateTransform((float)bmp.Width / 2, (float)bmp.Height / 2); //now rotate the image gfx.RotateTransform(rotationAngle); gfx.TranslateTransform(-(float)bmp.Width / 2, -(float)bmp.Height / 2); //set the InterpolationMode to HighQualityBicubic so to ensure a high //quality image once it is transformed to the specified size gfx.InterpolationMode = InterpolationMode.HighQualityBicubic; //now draw our new image onto the graphics object gfx.DrawImage(img, new Point(0, 0)); //dispose of our Graphics object gfx.Dispose(); //return the image return bmp; } } }
Ответы
Ответ 1
Если посмотреть исходники .NET Framework, то видно, что вызов Icon.FromHandle(IntPtr) транслируется в вызов Icon(IntPtr, takeOwnership=false). Чтобы избежать утечки памяти, надо для каждого IntPtr, который был получен при вызове GetHicon, вызывать DestroyIcon, после того как IntPtr перестал быть нужен. [DllImport("user32.dll", CharSet = CharSet.Auto)] extern static bool DestroyIcon(IntPtr handle); И у Bitmap, который больше не нужен, надо вызывать метод Dispose(); Чтобы каждый раз по таймеру не создавать новые Bitmap, как я уже говорил в комментарии тут, надо создать необходимые изображения, собрать их в коллекцию и использовать их для вывода в tray. Можно конвертировать Bitmap в Icon не вызывая GetHicon. см. "Fast and high quality Bitmap to icon converter". UPDATE using System.Drawing; using System.Collections.Generic; using System.Runtime.InteropServices; class Pic : IDisposable { [DllImport("user32.dll", CharSet = CharSet.Auto)] extern static bool DestroyIcon(IntPtr handle); // см. ниже @ public readonly int Angle; public readonly Bitmap Image; IntPtr Hicon; public Icon Icon; Bitmap RotateImage(Image prev, int an) { throw new NotImplementedException(); // todo } public Pic(int angle, Image original) { this.Image = RotateImage(original, angle); this.Angle = angle; this.Hicon = this.Image.GetHicon(); this.Icon = Icon.FromHandle(this.Hicon); } public void Dispose() { if (this.Hicon == IntPtr.Zero) return; this.Icon.Dispose(); DestroyIcon(this.Hicon); // см. ниже @ this.Hicon = IntPtr.Zero; this.Image.Dispose(); } } public partial class Form1 : Form { Dictionarypics; int an = 0; public Form1() { pics = new Dictionary (); pics.Add(new Pic(0, Image=notifyIcon1.Icon.ToBitmap()) ); } void timer1_Tick(object sender, EventArgs e) { var prev = pics[an]; if (an >= 350) an = 0; else an += 10; Pic p; if (pics.TryGetValue(an, out p) == false) { p = new Pic(an, prev.Image); pics.Add(p); } notifyIcon1.Icon = p.Icon; } } @ для гарантированного вызова DestroyIcon надо использовать специальную обертку над неуправляемым ресурсом. Пример -- тут. Ответ 2
Это не совсем утечка памяти. Со временем мусор будет чиститься, а память освобождаться. Однако, для объектов, реализующих IDisposable надо вызывать Dispose для освобождения ресурсов (в первую очередь unmanaged-ресурсов), как только объект стал не нужен. Вместо явного вызова Dispose следует использовать блок using, поскольку в нём Dispose вызывается в finally, т. е. его вызов почти гарантирован. Кроме того, это более красивая конструкция в плане кода. Что касается приведённого кода, то, во-первых, Bitmap обладает unmanaged-ресурсами, но твой код не пытается освободить их вызовом Dispose - ты уничтожаешь только Graphics. Однако, я бы действовал другим способом. Ясно, что есть ограниченное количество картинок, поэтому я бы сразу создал массив Bitmap'ов с разными поворотами, а в таймере просто менял иконку. Нет смысла на каждой итерации выполнять поворот заново. И сомнения вызывает необходимость преобразования Icon в Bitmap.
Комментариев нет:
Отправить комментарий