【问题标题】:Think I have a memory leak认为我有内存泄漏
【发布时间】:2016-03-15 18:48:26
【问题描述】:

遇到这个错误:

“System.OutOfMemoryException”类型的异常发生在 System.Drawing.dll 但未在用户代码中处理

附加信息:内存不足。

它发生在DrawImage调用的以下方法中

/// <summary>
        /// Resize the image to the specified width and height.
        /// </summary>
        /// <param name="image">The image to resize.</param>
        /// <returns>The resized image.</returns>
        public Bitmap ResizeImage(Image image, System.Drawing.Size newSize)
        {
            var destRect = new Rectangle(0, 0, newSize.Width, newSize.Height);
            var destImage = new Bitmap(newSize.Width, newSize.Height);

            destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);

            using (var graphics = Graphics.FromImage(destImage))
            {
                graphics.CompositingMode = CompositingMode.SourceCopy;
                graphics.CompositingQuality = CompositingQuality.HighQuality;
                graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
                graphics.SmoothingMode = SmoothingMode.HighQuality;
                graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;

                using (var wrapMode = new ImageAttributes())
                {
                    wrapMode.SetWrapMode(WrapMode.TileFlipXY);
                    graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
                }
            }

            return destImage;
        }

我不知道为什么会这样,我多次调用这个方法,结果被转换成 Base64 字符串并存储在 ObservableCollection 中。

/// <summary>
        /// Convert Image and Resize
        /// </summary>
        /// <param name="loc"></param>
        /// <returns></returns>
        public async Task<string> GenerateThumbnailBinary(string loc)
        {
            return await Task<string>.Factory.StartNew(() =>
            {
                Image image = Image.FromFile(loc, true);

                // Figure out the ratio
                double ratioX = (double)Properties.Settings.Default.ThumbnailWidth.Width / (double)image.Width;
                double ratioY = (double)Properties.Settings.Default.ThumbnailWidth.Height / (double)image.Height;
                // use whichever multiplier is smaller
                double ratio = ratioX < ratioY ? ratioX : ratioY;

                System.Drawing.Size newSize = 
                new System.Drawing.Size(
                    (int)(image.Width * ratio), 
                    (int)(image.Height * ratio));

                Image resized = ResizeImage(image, newSize);

                return ImageToBase64(resized, ImageFormat.Jpeg);
            });
        }

我还通过绑定到集合并使用转换器将 Base64 字符串转换回位图,将每个字符串显示为图像,这只是为了让 UI 显示已转换的内容。

我的问题从哪里开始?当我在 UI 上显示图像并使用转换器将字符串转换为图像时,我是否会尝试在内存中存储太多图像?

下图中的高点在运行方法循环时很明显,但它似乎仍然比方法运行结束前高,这有帮助吗?

编辑: 这是启动任务并运行方法的循环。

// Generate List of images to upload
                var files = Directory.EnumerateFiles(sel.Name, "*.*", SearchOption.AllDirectories)
                        .Where(s => s.EndsWith(".jpeg") ||
                        s.EndsWith(".jpg") ||
                        s.EndsWith(".png") ||
                        s.EndsWith(".JPG"));
                int b = 1;

                if (files.Count() > 0)
                {
                    /// <summary>
                    /// Resize Images
                    /// </summary>
                    /// Set current Task first
                    UploadTask = Steps[0].Message;
                    try
                    {
                        foreach (string item in files)
                        {
                            // Generate new name
                            string oldname = Path.GetFileNameWithoutExtension(item);
                            string newName = Common.Security.KeyGenerator.GetUniqueKey(32);
                            string t = await GenerateThumbnailBinary(item);

                            ImageUploadObjects.Add(
                                new ImageUploadObject { OldName = oldname,
                                    NewName = newName,
                                    ByteImage = t });

                            UploadProgress = (int)Math.Round((double)(100 * b / files.Count()));
                            b++;
                        }
                        // Complete
                        Steps[0].Complete = true;
                    }
                    catch(Exception e)
                    {
                        Steps[0].Error = e.InnerException.ToString();
                    }


                    /// <summary>
                    /// Move full resoluation images
                    /// </summary>
                    /// Set current Task first
                    UploadTask = Steps[1].Message;
                    try
                    {
                        foreach (string item in files)
                        {

                        }
                        // Complete
                        Steps[1].Complete = true;
                    }
                    catch (Exception e)
                    {
                        Steps[1].Error = e.InnerException.ToString();
                    }
                }
            }

编辑:

如何判断内存是否仍在使用?我在下面添加了另一张图片,第一个快照是在我执行该方法之前,最后一个是它完成时,2 - 4 是它正在运行的时候

【问题讨论】:

  • 在处理位图和绘图时,由于很多原因,您可能会收到 OutOfMemoryException,例如 stackoverflow.com/questions/6506089/…(我并不是说这是重复的,只是为了看看)。跨度>
  • 您需要处理一些变量(图像和调整大小)。通过调用.Dispose() 或使用using 关键字。注意不要同时调用这个方法太多次。
  • 图片大小是否合理?
  • 我会确保我处理变量,是的,大小都是一样的

标签: c# winforms memory-leaks


【解决方案1】:

我认为解决您的问题的最简单方法是分块/分批进行。例如,如果您有 100 个文件,那么您正在创建 100 个任务,这些任务将文件内容加载到内存中的图像中。也许做 10(或其他数字),一旦完成,再做下一个 10(或其他数字)。我相信这将解决您的问题。

还要确保在任何实现 Disposable 的类(即 Image 和 Bitmap 等)上调用 Dispose。

除上述内容外,以下是您要尝试执行的简要操作: 1.读取一个目录并取出所有文件。 2. 为每个文件创建缩略图。 3. 将缩略图添加到内存中的集合中。 4. 将图像转移到另一个位置。

对于上面的第 2 项,我不会将所有缩略图都保存在内存中。即使我需要在 UI 中显示这一点,我也会根据需要合并分页并拉取它们。

【讨论】:

  • 它只是有时会发生,对较慢的机器进行测试的最佳方法是什么?我的系统中有 32GB 的 2133MHz,所以很难判断一个更便宜的系统是否不能很好地处理它。
  • 查看编辑后的答案。我认为您可以按照建议改进您的设计,然后再担心在较慢的机器上进行测试。
  • 我已经把它们缩小了很多(大约 200 像素宽)并且它们被存储为一个字符串,所以不认为显示它们会是一个很大的问题,永远不会超过 100 个老实说,应该永远少于那个。
  • 好的。那么接下来我要做的就是只用 1 个文件测试我的代码。 Here 你可以阅读更多关于哪些对象被植根以及为什么(阅读根路径部分)。在不再有根之前,不会收集有根对象。在创建缩略图之前拍摄快照,并查看植根的内容。然后在创建缩略图后拍摄快照。比较 2(手动)以查看仍然扎根的内容。如果您有一些对象因为它们植根于一个文件而没有被收集,那么对于许多对象来说也是如此。试试看。
  • 谢谢,明天试试这个,上班一定要睡一觉!
【解决方案2】:

Image resized = ResizeImage(image, newSize) 未被处理。因为在运行终结器线程之前不会释放分配的内存,因此您可能会到处泄漏内存。

Image image = Image.FromFile(loc, true);
...

Image resized = ResizeImage(image, newSize);
image.Dispose();
string base64Image = ImageToBase64(resized, ImageFormat.Jpeg);
resized.Dispose();
return base64Image;

【讨论】:

  • 我必须退回它,我该如何处理它?
  • 你没有返回它 - 你正在将它传递给 ResizeImage 函数。它是您返回的 ResizeFunction 的返回值。图片调整大小 = ResizeImage(image, newSize); image.Dispose();返回调整大小;
  • 那么,如果我改为:Image resized = ResizeImage(image, newSize); image.Dispose(); string k = ImageToBase64(resized, ImageFormat.Jpeg); resized.Dispose();return k; 会更好吗?当我处理图像并使用字符串从任务返回时。
  • 如何判断内存是否仍在使用?我在帖子中添加了另一个图像,第一个快照是在我执行该方法之前,最后一个是当它完成时,2 - 4 在它运行时
  • 我添加了一个代码 sn-p 来处理我的答案中的非托管资源。实施此更改时,您是否仍然收到 OutOfMemoryException ?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-25
  • 2011-12-25
相关资源
最近更新 更多