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

时间:2015-06-08 13:49:20

标签: c# memory-leaks bitmap garbage-collection

背景信息

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

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

    为了管理内存使用,我创建了一个CompressibleImage类,它封装了一个Bitmap文件,并在FileStream上读/写图像文件。当应用程序不再需要图像时,它将被写入文件流。当应用程序需要图像时(即用户双击缩略图),将从流中创建位图文件。以下是主要的 CompressibleImage&#39> 方法:

    /// 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. 将位图写入文件流
    4. 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的文件)。

      问题是,当我尝试在其他机器上测试应用程序时,垃圾收集器不会释放内存,在执行这两种方法时会导致 MemoryException 图像量相当高(> 40)。当我尝试解压缩(读取)图像时,在 CompressibleImage.GetDecompressedImage()方法中抛出异常:

      decompressedImage = new Bitmap(stream);
      

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

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

      系统信息

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

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

2 个答案:

答案 0 :(得分:1)

对MemoryException不太确定,请提供完整的堆栈跟踪。

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

这就是为什么你应该在dispose方法中使用'disposing'标志,并避免在执行来自终结器时触摸托管对象。

下面的示例显示了实现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.
    }
}

答案 1 :(得分:-1)

使用包含/* 1: apply the function in the digest cycle */ $scope.$apply(function() { $scope.checkShopping = true; }); /* 2: apply to the whole digest cycle */ $scope.checkShopping = true; $scope.$apply(); /* 3: use an object with initialization */ $scope.shopping = { checkShopping: false } // then the following will be picked up by angular's digest cycle: $scope.shopping.checkShopping = true; 接口的类打开文件或流时,通常应使用IDisposable。这将确保在using语句之后调用using方法。如果正确实施,这将确保释放非托管资源。

MSDN Article on 'using' statement