【问题标题】:C#: ImageList in foreach loop causes application to crash on terminal serverC#:foreach 循环中的 ImageList 导致应用程序在终端服务器上崩溃
【发布时间】:2014-09-30 16:16:46
【问题描述】:

我是一个(小型)开发团队的一员,负责开发和支持 C#.Net 程序。它有 ~ 300.000 LOC 并且软件设计使得不可能改变任何大的东西而不引起数百万的副作用。这就像试图将充满毒蛇的丛林变成一个漂亮的小花园。只用一把小剪刀。

该应用程序是具有数据库访问权限的大型 WinForms 应用程序。

关于问题:客户收到我们的软件但无法运行。与其他客户不同,他们拥有多个 Windows Server 2008 R1 终端服务器,并将软件安装在网络驱动器上。他们的用户连接到其中一台终端服务器并从网络驱动器运行我们的应用程序(以及其他应用程序,如 windows office 等)。然而,我们的应用程序在大约 5 秒后崩溃,没有任何通知。我们的加载屏幕出现并再次关闭。应用程序生成一个显示此异常的日志文件:

2014-08-04 11:15:23 [3372] ERROR – An exception occurred:
'OutOfMemoryException': Not enough memory. 
System.Drawing

at System.Drawing.Graphics.CheckErrorStatus(Int32 status)
at System.Drawing.Graphics.DrawImage(Image image, Rectangle destRect, Int32 srcX, Int32 srcY, Int32 srcWidth, Int32 srcHeight, GraphicsUnit srcUnit, ImageAttributes imageAttrs, DrawImageAbort callback, IntPtr callbackData)
at System.Drawing.Bitmap.MakeTransparent(Color transparentColor)
at System.Windows.Forms.ImageList.CreateBitmap(Original original, Boolean& ownsBitmap)
at System.Windows.Forms.ImageList.CreateHandle()
at System.Windows.Forms.ImageList.get_Handle()
at System.Windows.Forms.ImageList.GetBitmap(Int32 index)
at System.Windows.Forms.ImageList.ImageCollection.GetEnumerator()
at <our application>.InitializeHtmlResources()

调用ImageCollection.GetEnumerator()的方法是这样的:

void InitializeHtmlResources()
    {
        string baseDirPath = ... //It's in AppData/Local/<ourFolder>/Icons;


        int index = -1;
        foreach (Image image in UIResources.Icons.Images)
        {
            index += 1;

            image.Save(baseDirPath + Path.DirectorySeparatorChar + index.ToString(), ImageFormat.Png);
        }
    }

这些图像存储在 Resource.Icons.dll 中。图标在解决方案中有自己的项目,其中包含一些帮助类(如 UIResources),并包含一个文件夹,其中包含我们使用的每个图标 + 一个 xml,它们都被列出、命名和索引。 UIResources 是一个允许访问图标和图像列表的静态类。这就是属性“Images”的初始化方式:

...
// Code snippet of Images Initialization
var ilist = new ImageList();
ilist.ColorDepth = ColorDepth.Depth32Bit;
ilist.ImageSize = new Size(16, 16);
ilist.TransparentColor = Color.Fuchsia;
UIResources.Icons.Images = ilist;
...

此方法用于从 DLL 文件中提取图标。

static IEnumerable<IconInfo> GetIcons()
    {
        XDocument doc;
        using (var stream = GetResourceStream("Our.Namespace.Resources.Icons.Icons.xml"))
        {
            doc = XDocument.Load(stream);
        }

        // ReSharper disable once PossibleNullReferenceException            
        foreach (var elem in doc.Element("Icons").Elements("Icon"))
        {
            int index = (int)elem.Attribute("Index");
            var bmp = ReadIcon("Icons", (string)elem.Attribute("FileName"));
            string name = (string)elem.Attribute("Name");

            yield return new IconInfo(bmp, index, name);
        }

    }
    static Bitmap ReadIcon(string kind, string fileName)
    {
        using (var stream = GetResourceStream("Our.Namespace.Resources." + kind + "." + fileName))
        {
            return new Bitmap(stream);
        }
    }

    static Stream GetResourceStream(string resourceName)
    {
        return typeof(IconProvider).Assembly.GetManifestResourceStream(resourceName);
    }

IconInfo 只是一个包含值的记录。

最后,ImageList 填充了值:

foreach (var icon in GetIcons())
            UIResources.Icons.Images.Add(icon.Name, icon.Image);

应用程序运行良好。但是,当该客户通过远程桌面在其终端服务器上运行软件时,应用程序崩溃并在 foreach 循环(访问 ImageList 的枚举器时)的 InitializeHtmlResources() 中引发 OutOfMemory 异常。

令人困惑的部分:虽然内存未满,也未达到 32 位应用程序的 2 GB 限制,但会引发“OutOfMemory”异常。该应用在加载期间达到 120 MB 的峰值。

我完全不知道这个错误是如何引起的,并在过去的 2-3 天里试图找到解决方案。我没有。

感谢您给我的每一个建议。

编辑:

我尝试禁用 InitializeHtmlResources-Method。这使应用程序能够启动。但是:在使用该应用程序几秒钟后,仍然出现了内存不足异常。原因是另一个 ImageList 访问器。

它在 Server 2012 上运行良好。我们使用 Windows Server 2008 创建了一个 VM,错误也发生在那里。

【问题讨论】:

  • 只有一条评论,但我想知道是否因为您正在退出 using 块而没有处理流。
  • 我一直认为 using 子句会确保它在任何情况下都被处理,包括异常等。你知道在崩溃之前加载了多少图标吗?您可以注销 Icon.Name 吗?一共有多少个图标?
  • 我刚刚测试过:不,不是这样。图像的整个加载工作正常。整个事情只有在 foreach 循环开始时才会崩溃
  • @TaW 阅读我的评论。返回在 using 块中,因此它永远不会真正完成 using 块。
  • @Blam:这里是事件的顺序,直截了当: using 语句只是 try/finally 块的语法糖,正如 Grzenio 所说,从也尝试阻止。将计算返回表达式,然后执行 finally 块,然后方法将返回。 Jon Skeet's answer

标签: c# .net icons windows-server-2008 gdi


【解决方案1】:

我们找到了问题!而不是

UIResources.Icons.Images.Add(icon.Name, icon.Image);

为了填充 ImageList,我们现在使用

UIResources.Icons.Images.Add(icon.Name, new Bitmap(icon.Image));

现在我们的应用程序可以在 Windows Server 2008 上运行 :-)

【讨论】:

    【解决方案2】:

    【讨论】:

    • 我们的程序甚至没有接近 32 位程序的 2GB 限制。
    猜你喜欢
    • 1970-01-01
    • 2015-08-17
    • 2013-03-11
    • 1970-01-01
    • 1970-01-01
    • 2012-04-26
    • 1970-01-01
    • 2013-07-25
    • 2013-01-16
    相关资源
    最近更新 更多