System.Drawing.Bitmap

时间:2017-10-18 06:04:23

标签: c# .net out-of-memory .net-4.5

有没有办法在运行时获取有关OutOfMemoryException的更多详细信息?或者,这个异常是否可以不被封闭的try / catch捕获,而是在调用堆栈中更高的try / catch?我无法使用WinDBG重现,所以它必须是我可以从应用程序中记录的东西。

我为长篇解释道歉,但有很多可能的原因要消除,我解释一下。

我已经阅读了OutofMemoryException的所有可能性,并基本上消除了所有这些可能性。通常情况下,应用程序运行良好,但偶尔只在某些计算机上,我得到一个OutOfMemoryException。由于这些报告在本地不可再现,我只有原木可供使用。但我有相当多的细节。

奇怪的是:

  • 任何逻辑上可能在附近分配内存的东西都在try / catch中,但异常被视为未处理(并且在调用堆栈中捕获的位置更高)
  • 没有使用StringBuffers
  • 即使重新启动并重新启动应用程序,也会发生异常。
  • 仅在几分钟后发生异常,并且只分配了大约30MiB的内存,块数不超过1.5MiB。
  • 已验证应用程序(专为"任何"处理器构建)运行为64位。
  • 不缺磁盘空间(270Gb免费)并启用了页面文件。
  • 似乎不是可能的LoH碎片问题。

最近在应用程序的不同部分发生过几次这种情况。第一次,我得出结论,有一个损坏的.NET程序集,因为它首先加载System.Web.Serialization程序集时发生异常。我可以确定它是在第一次使用该程序集的方法调用期间发生的。重新映像计算机(与原始设置完全相同)和更新窗口解决了这个问题。

但是,对我来说,似乎不太可能在几天内发生的第二个案例,不同的客户也是腐败。这个发生在没有装载程序集的位置。我现在正在重新思考第一个错误。我所知道的:

  • 它发生在线程池线程中(System.Timers.Timer,[Statthread])
  • 活跃的少量线程(< 5)
  • 它发生在下载1MiB文件的时候。这被读入MemoryStream,因此可能大到2MiB。然后将其提供给System.Drawing.Bitmap构造函数,从而生成大约8MiB的Bitmap。但是,这一切都在捕获System.Exception的try / catch中。 try / catch中唯一没有的是返回byte []引用,它应该只是一个引用副本,而不是任何内存分配。
  • 在此之前没有进行过其他重要的内存分配。在本地版本中查看应该运行相同的堆,只显示应用程序图标和几个将在小对象堆上的对象。
  • 在具有特定输入的特定系统上可重复。但是,这些系统是相互克隆的。只有明显的变化才是Windows更新的顺序。
  • 我正在运行的程序集已签名。是不是有校验和确保它没有被破坏?所有系统组件都一样吗?我不知道如何通过损坏的dll甚至数据来解释这个实例。
  • 在异常时查看调用堆栈是非常无益的。我在下面的代码中指出抛出异常的地方。
  • 有一些COM对象的使用。但是,在正常情况下,我们运行应用程序几周没有内存问题,当我们得到这些异常时,它们几乎是立即的,只使用了大约20个相对轻量级的COM对象(IUPnPDevice)

    // Stack Trace indicates this method is throwing the OutOfMemoryException
    // It isn't CAUGHT here, though, so not in the try/catch.
    //
    internal void Render(int w, int h)
    {
        if (bitmap != null)
        {
            bitmap.Dispose();
            bitmap = null;
        }
    
        if (!String.IsNullOrEmpty(url))
        {
        // this information is printed successfully to log, with correct url
        // exception occurs sometime AFTER this somehow.
            Logger.Default.LogInfo("Loading {0}", url);
    
        // when file contents changed (to go from 1MiB to 500MiB, the error went away)
            byte[] data = DownloadBinaryFile(url);
            if (data != null)
            {
                try
                {
                    Bitmap bmp;
                    using (var ms = new MemoryStream(data))
                    {
                        bmp = new Bitmap(ms);
                    }
                    bitmap = bmp;
                }
                catch (Exception)
                {
                    // We do not catch anything here.
                    Logger.Default.LogWarning("WARNING: Exception loading image {0}", url);
                }
            }
    
            //
            // if we had any errors, just skip this slide
            //
            if (bitmap == null)
            {
                return;
            }
    
            // QUESTION EDIT:
            // in the problematic version, there was actually an unnecessary
            // call here that turns out to be where the exception was raised:
            using( Graphics g = Graphics.FromImage(bitmap)) {
            }
    
        }
    }
    
    // calling this would trigger loading of the System.Web assembly, except
    // similar method has been called earlier that read everything. Only
    // class being used first time is the BinaryReader, which is in System.IO 
    // and already loaded.
    internal static byte[] DownloadBinaryFile(String strURL, int timeout = 30000)
    {
        try
        {
            HttpWebRequest myWebRequest = HttpWebRequest.Create(strURL) as HttpWebRequest;
    
            myWebRequest.KeepAlive = true;
            myWebRequest.Timeout = timeout;
            myWebRequest.ReadWriteTimeout = timeout;
            myWebRequest.UserAgent = "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)";
    
            Encoding encode = System.Text.Encoding.GetEncoding("utf-8");
    
            using (HttpWebResponse myWebResponse = myWebRequest.GetResponse() as HttpWebResponse)
            {
                if (myWebResponse.StatusCode != HttpStatusCode.OK)
                {
                    Logger.Default.LogWarning("WARNING: Response {0} loading {1}", myWebResponse.StatusCode, strURL);
                    return null;
                }
    
                using (Stream receiveStream = myWebResponse.GetResponseStream())
                {
                    using (BinaryReader readStream = new BinaryReader(receiveStream))
                    {
                        // this extension method uses MemoryStream, but seems irrelevant since we don't catch anything here.
                        return readStream.ReadAllBytes();
                    }
                }
            }
        }
        catch (Exception e)
        {
            // we do not catch anything here.
            Logger.Default.LogError("ERROR: Exception {0} loading {1}", e.Message, strURL);
        }
        return null;
    }
    

所以,在所有这些之后,我回到我的开场白问题。在我可以检查的OutOfMemoryException对象上是否有任何已知属性,或者在抛出异常后我可以进行调用,以缩小它的范围?

并且..是否有任何原因导致OutOfMemoryException不会被第一个try / catch捕获,但会被调用堆栈进一步捕获?

1 个答案:

答案 0 :(得分:4)

谢谢大家。答案有点好奇,我有一些细节错误,这让人难以理解。错误在这里:

            Bitmap bmp;
            using (var ms = new MemoryStream(data))
            {
                bmp = new Bitmap(ms);
            }
            bitmap = bmp;

在Bitmap构造函数的文档备注中,我发现了这个:

  

您必须在Bitmap的生命周期内保持流打开。

显然,在构建之后立即关闭MemoryStream违反了这一点。在我和实际使用Bitmap之间的垃圾收集显然是在创建错误。 (编辑:实际上,似乎在1MiB周围存在一个边界,其中FromStream函数最初只会解压缩JPEG文件的大部分。对于JPEG< 1MiB,整个图像被解压缩并且它实际上并没有使用该流在初始化之后。对于较大的JPEG,在需要这些像素之前,它将不会超过第一个1MiB)

我很难想象微软为何会这样做。我也不想打开原始流(这是一个http连接)所以我看到的唯一解决方案是克隆位图:

// Create a Bitmap object from a file.
using (var ms = new MemoryStream(data))
{
   bmp = new Bitmap(ms);
   Rectangle cloneRect = new Rectangle(0, 0, bmp.Width, bmp.Height);
   System.Drawing.Imaging.PixelFormat format = bmp.PixelFormat;
   this.bitmap = bmp.Clone(cloneRect, bmp.PixelFormat);    
}

导致我长期令人沮丧的搜索以及排除关键信息的原因是,在客户端计算机上执行的代码是稍微旧版本,只有一个微妙的变化。在前一版本的位图上调用了Graphics.FromImage(),但是已经删除了。尽管如此,这个版本在绝大多数时间里运作良好。