不同机器上的C#内存泄漏
本文关键字:内存 泄漏 机器 | 更新日期: 2023-09-27 17:54:08
背景信息
我用Windows窗体(C#(开发了一个桌面应用程序,用于扫描、预览和保存图像。扫描时的应用程序行为如下:
- 扫描n图像
- 为每个图像获取位图并将其存储在临时文件中
- 将调整大小的缩略图显示为预览
图像内存管理:可压缩图像
为了管理内存使用,我创建了一个CompressibleImage类,它封装了位图文件并在FileStream上读取/写入图像文件。当应用程序不再需要图像时,会将其写入文件流。当应用程序需要图像时(即用户双击缩略图(,会从流中创建位图文件。以下是CompressibleImage的主要方法:
/// Gets the uncompressed image. If the image is compressed, it will be uncompressed
public Image GetDecompressedImage()
{
if (decompressedImage == null)
{
// Read Bitmap from file stream
stream.Seek(0, SeekOrigin.Begin);
decompressedImage = new Bitmap(stream);
}
return decompressedImage;
}
/// Clears the uncompressed image, leaving the compressed one in memory.
public void ClearDecompressedImage()
{
// If Bitmap file exists, write it to file and dispose it
if (decompressedImage != null)
{
if (stream == null)
{
stream = new FileStream(FileStreamPath, FileMode.Create);
}
decompressedImage.Save(stream, format);
// The Dispose() call does not solve the issue
// decompressedImage.Dispose();
decompressedImage = null;
}
}
/// <summary>
/// Class destructor. It disposes the decompressed image (if this exists),
/// closes the stream and delete the temporary file associated.
/// </summary>
~CompressibleImage()
{
if (decompressedImage != null)
{
decompressedImage.Dispose();
}
if(stream != null)
{
stream.Close();
File.Delete(stream.Name);
stream.Dispose();
}
}
应用程序级别
该应用程序主要在扫描方法和保存过程中使用CompressibleImage创建图像文件。扫描方法工作良好,基本上:
- 从扫描仪获取位图
- 从扫描的位图创建可压缩图像
- 将位图写入文件流
save方法在我的机器上运行良好,其行为如下:1.对于每个CompressibleImage解压缩(读取和构建(流中的位图2.保存图像3.压缩图像
这是保存的方法:
private void saveImage_button_Click(object sender, EventArgs e)
{
if (Directory.Exists(OutputPath) == false && File.Exists(OutputPath) == false)
{
Directory.CreateDirectory(OutputPath);
}
ListView.CheckedListViewItemCollection checkedItems = listView1.CheckedItems;
if(checkedItems.Count > 0)
{
for (int i = 0; i < checkedItems.Count; ++i)
{
int index = checkedItems[i].Index;
Bitmap image = (Bitmap)compressibleImageList.ElementAt(index).GetDecompressedImage();
try
{
image.Save(OutputPath + index.ToString() +
Module.PNG_FORMAT, ImageFormat.Png);
compressibleImageList.ElementAt(index).ClearDecompressedImage();
progressForm.Increment();
image = null;
}
catch (Exception ex) {
...
}
}
}
}
问题描述
在我的机器中,应用程序运行良好。没有内存泄漏,扫描和保存方法可以顺利完成任务,并具有合理的内存使用率(扫描100张纸张,拾取量小于<140MB(。
问题是,当我试图在其他机器上测试应用程序时,垃圾回收器没有释放内存,导致在执行这两个方法期间以及当图像量相当高时(>40(出现MemoryException。异常在CompressibleImage内部引发。当我尝试解压缩(读取(图像时,GetDecompressedImage((方法:
decompressedImage = new Bitmap(stream);
虽然我知道GC是随机清理内存的,但在这种情况下,它似乎甚至没有运行,事实上,只有当我关闭应用程序时,内存才会释放。
在类似的机器上可能会有如此不同的行为?
系统信息
以下是有关测试环境的一些信息。两台机器都有:
- 处理器:Intel i7 2.30GHz
- 内存:8GB
- 类型:64位
- 操作系统:Windows 7 Pro SP 1
不太确定您的MemoryException,请提供完整的堆栈。
然而,我可以看出你在析构函数中犯了一个明显的错误。您不应该在Destructor中引用您的托管资源。原因是GC和Finalizer使用启发式算法来触发它们,而您永远无法预测托管对象的终结器的执行顺序。
这就是为什么您应该在dispose方法中使用"dispose"标志,并避免在执行来自终结器时接触托管对象。
下面的示例显示了实现IDisposable接口的一般最佳实践。参考:https://msdn.microsoft.com/en-us/library/system.idisposable.dispose(v=vs.110(.aspx
public class DisposeExample
{
// A base class that implements IDisposable.
// By implementing IDisposable, you are announcing that
// instances of this type allocate scarce resources.
public class MyResource: IDisposable
{
// Pointer to an external unmanaged resource.
private IntPtr handle;
// Other managed resource this class uses.
private Component component = new Component();
// Track whether Dispose has been called.
private bool disposed = false;
// The class constructor.
public MyResource(IntPtr handle)
{
this.handle = handle;
}
// Implement IDisposable.
// Do not make this method virtual.
// A derived class should not be able to override this method.
public void Dispose()
{
Dispose(true);
// This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SupressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
// Dispose(bool disposing) executes in two distinct scenarios.
// If disposing equals true, the method has been called directly
// or indirectly by a user's code. Managed and unmanaged resources
// can be disposed.
// If disposing equals false, the method has been called by the
// runtime from inside the finalizer and you should not reference
// other objects. Only unmanaged resources can be disposed.
protected virtual void Dispose(bool disposing)
{
// Check to see if Dispose has already been called.
if(!this.disposed)
{
// If disposing equals true, dispose all managed
// and unmanaged resources.
if(disposing)
{
// Dispose managed resources.
component.Dispose();
}
// Call the appropriate methods to clean up
// unmanaged resources here.
// If disposing is false,
// only the following code is executed.
CloseHandle(handle);
handle = IntPtr.Zero;
// Note disposing has been done.
disposed = true;
}
}
// Use interop to call the method necessary
// to clean up the unmanaged resource.
[System.Runtime.InteropServices.DllImport("Kernel32")]
private extern static Boolean CloseHandle(IntPtr handle);
// Use C# destructor syntax for finalization code.
// This destructor will run only if the Dispose method
// does not get called.
// It gives your base class the opportunity to finalize.
// Do not provide destructors in types derived from this class.
~MyResource()
{
// Do not re-create Dispose clean-up code here.
// Calling Dispose(false) is optimal in terms of
// readability and maintainability.
Dispose(false);
}
}
public static void Main()
{
// Insert code here to create
// and use the MyResource object.
}
}
当使用包含IDisposable
接口的类打开文件或流时,通常应该使用using
。这将确保在using
语句之后调用Dispose
方法。如果正确实现,这将确保释放非托管资源。
MSDN关于"使用"语句的文章