#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.
Комментариев нет:
Отправить комментарий