不同机器上的C#内存泄漏

尼克特

背景资料

我已经使用Windows窗体(C#)开发了一个桌面应用程序,用于扫描,预览和保存图像。扫描时应用程序的行为如下:

  1. 扫描n张图像
  2. 获取每个图像的位图并将其存储在临时文件中
  3. 将调整大小的缩略图显示为预览

图像内存管理:可压缩图像

为了管理内存使用,我创建了CompressibleImage类,该类封装了Bitmap文件并在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创建图像文件。扫描方法可以正常工作,并且基本上可以:

  1. 从扫描仪获取位图
  2. 从扫描的位图创建一个CompressibleImage
  3. 将位图写入文件流

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 MB的100张纸)。

问题是,当我尝试在其他计算机上测试该应用程序时,垃圾收集器不会释放内存,这两种方法都将在执行期间以及图像数量足够高(> 40)时引起MemoryException当我尝试解压缩(读取)图像时,CompressibleImage.GetDecompressedImage()方法内引发了异常

decompressedImage = new Bitmap(stream);

尽管我知道GC会随机清理内存,但是在这种情况下,它似乎甚至无法运行,并且实际上只有在我关闭应用程序时才会释放内存。

在类似的机器上可能有这种不同的行为吗?

系统信息

以下是有关测试环境的一些信息。两台机器都具有:

  • 处理器:Intel i7 2.30GHz
  • 内存:8GB
  • 类型:64位
  • 操作系统:Windows 7 Pro SP 1
查理斯

对您的MemoryException不太确定,请提供完整的stacktrace。

但是,我可以看到您在析构函数中犯了一个明显的错误。您不应在析构函数中引用托管资源。原因是,GC和Finalizer使用启发式算法来触发它们,并且您永远无法预测托管对象的终结器的执行顺序。

这就是为什么您应该在dispose方法中使用“ dispose”标志,并在执行来自终结器的情况下避免触摸托管对象的原因。

下面的示例显示了实现IDisposable接口的一般最佳实践。参考:https : //msdn.microsoft.com/zh-cn/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.
    }
}

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

不同机器上的C#内存泄漏

来自分类Dev

不同机器上的不同内核版本

来自分类Dev

不同机器上的DNS和DHCP

来自分类Dev

sklearn与statsmodels和sklearn在不同机器上的结果不同

来自分类Dev

不同机器python代码上的不同输出

来自分类Dev

在不同机器上具有不同输出的并行代码

来自分类Dev

EPPlus AutoFit()在不同机器上的不同列宽

来自分类Dev

Socket.Poll在不同机器上的延迟差异很大

来自分类Dev

不同机器上的RabbitMQ经纪人

来自分类Dev

Socket.Poll在不同机器上的延迟差异很大

来自分类Dev

在不同机器上编辑相同文件的建议

来自分类Dev

不同机器上的 strptime 不一致

来自分类Dev

我如何设置可以在不同机器上使用的 Jenkins?

来自分类Dev

有没有办法让不同机器上的多个用户从 C# WinForm 程序将表单输入写入 SharePoint 上的同一个 Excel 文件?

来自分类Dev

连接在不同机器和不同网络上运行的两个不同的Ubuntu

来自分类Dev

不同网络上不同机器上的Python代码并行化

来自分类Dev

来自不同机器的不同nmap结果?

来自分类Dev

C#内存泄漏与位图

来自分类Dev

在不同机器上绘制图元文件时的缩放比例不同

来自分类Dev

不同机器和操作系统上的应用程序行为不同

来自分类Dev

Keras > 在不同 Python 版本的不同机器上使用训练模型

来自分类Dev

单个静态IP后面的不同机器上的多个Web服务器

来自分类Dev

由于Java中的线程化,不同机器上的性能/时间问题

来自分类Dev

在不同机器上运行的服务器之间转发Thrift服务请求

来自分类Dev

Spring远程分区策略中如何在不同机器上启动slave

来自分类Dev

NS3 EMU可以在WAN中的不同机器上应用吗?

来自分类Dev

由于Java中的线程化,不同机器上的性能/时间问题

来自分类Dev

使用Mercurial:如何记录不同机器上发生的源代码更改

来自分类Dev

Laravel中不同机器上的视图和控制器

Related 相关文章

  1. 1

    不同机器上的C#内存泄漏

  2. 2

    不同机器上的不同内核版本

  3. 3

    不同机器上的DNS和DHCP

  4. 4

    sklearn与statsmodels和sklearn在不同机器上的结果不同

  5. 5

    不同机器python代码上的不同输出

  6. 6

    在不同机器上具有不同输出的并行代码

  7. 7

    EPPlus AutoFit()在不同机器上的不同列宽

  8. 8

    Socket.Poll在不同机器上的延迟差异很大

  9. 9

    不同机器上的RabbitMQ经纪人

  10. 10

    Socket.Poll在不同机器上的延迟差异很大

  11. 11

    在不同机器上编辑相同文件的建议

  12. 12

    不同机器上的 strptime 不一致

  13. 13

    我如何设置可以在不同机器上使用的 Jenkins?

  14. 14

    有没有办法让不同机器上的多个用户从 C# WinForm 程序将表单输入写入 SharePoint 上的同一个 Excel 文件?

  15. 15

    连接在不同机器和不同网络上运行的两个不同的Ubuntu

  16. 16

    不同网络上不同机器上的Python代码并行化

  17. 17

    来自不同机器的不同nmap结果?

  18. 18

    C#内存泄漏与位图

  19. 19

    在不同机器上绘制图元文件时的缩放比例不同

  20. 20

    不同机器和操作系统上的应用程序行为不同

  21. 21

    Keras > 在不同 Python 版本的不同机器上使用训练模型

  22. 22

    单个静态IP后面的不同机器上的多个Web服务器

  23. 23

    由于Java中的线程化,不同机器上的性能/时间问题

  24. 24

    在不同机器上运行的服务器之间转发Thrift服务请求

  25. 25

    Spring远程分区策略中如何在不同机器上启动slave

  26. 26

    NS3 EMU可以在WAN中的不同机器上应用吗?

  27. 27

    由于Java中的线程化,不同机器上的性能/时间问题

  28. 28

    使用Mercurial:如何记录不同机器上发生的源代码更改

  29. 29

    Laravel中不同机器上的视图和控制器

热门标签

归档