6

假设我必须在某些控件上显示一些图形。但是会根据某些条件切换三个图像。在资源文件中添加了三个位图。

因此,我通过调用 ResourceManager.GetObject 来检索它们。

问题是,它应该是:

  1. 每次我必须切换图像时,我都会调用 GetObject 来获取它并分配给控件或
  2. 在开始时为每个图像保存 GetObject 的结果,这样就只有 3 次调用 GetObject。而是从我的变量中分配图像。

使用 CLR Profiler 查看时,执行 1) 似乎会产生大量 GC 句柄。希望知道2)的任何不良副作用。

非常感谢。

4

5 回答 5

8

每次调用GetObject都会从程序集中读取图像并将其加载到Bitmap对象中。

多次调用它会产生很大的开销;你应该存储图像。

于 2010-10-29T02:28:26.920 回答
5

我有一个 WinForms 应用程序,它使用相同表单的许多实例,每个实例都有许多图像和图标,用于菜单和按钮等。所有这些图像都存储在自动生成的[ProjectName].Properties.Resources类中。

我注意到内存使用率非常高;在仅 10 个左右的 Form 实例之后,它使用了数百 MB 的内存,并且在多个实例之后很容易跨越 1+ GB。我将问题追溯到ResourceManager.GetObject方法。该GetObject方法返回请求的每个对象的新实例,这对我来说似乎是错误的。

与其让所有这些图像实例只占用内存而超出范围,为什么不将它们重用于未来的 Form 实例呢?所以我创建了一个自定义CachedResourceMananger类并覆盖了GetObject返回请求对象的缓存实例的方法。

 /// <summary>
/// A custom Resource Manager that provides cached instances of objects.
/// This differs from the stock ResourceManager class which always
/// deserializes and creates new instances of every object.
/// After the first time an object is requested, it will be cached
/// for all future requests.
/// </summary>
public class CachedResourceManager : System.Resources.ResourceManager
{
    /// <summary>
    /// A hashtable is used to store the objects.
    /// </summary>
    private Hashtable objectCache = new Hashtable();

    public CachedResourceManager(Type resourceSource) : base(resourceSource)
    {
    }

    public CachedResourceManager(string baseName, Assembly assembly) : base(baseName, assembly)
    {
    }

    public CachedResourceManager(string baseName, Assembly assembly, Type usingResourceSet) : base(baseName, assembly, usingResourceSet)
    {
    }

    public CachedResourceManager() : base()
    {
    }

    /// <summary>
    /// Returns a cached instance of the specified resource.
    /// </summary>
    public override object GetObject(string name)
    {
        return GetObject(name, null);
    }

    /// <summary>
    /// Returns a cached instance of the specified resource.
    /// </summary>
    public override object GetObject(string name, CultureInfo culture)
    {
        // Try to get the specified object from the cache.
        var obj = objectCache[name];

        // If the object has not been cached, add it
        // and return a cached instance.
        if (obj == null)
        {
            objectCache[name] = base.GetObject(name, culture);
            obj = objectCache[name];
        }

        return obj;
    }
}

然后我修改了自动生成的[ProjectName].Properties.Resources类中的资源管理器属性和字段以使用自定义资源管理器,替换global::System.Resources.ResourceManagerCachedResourceManager.

internal class Resources
{
    private static CachedResourceManager resourceMan;

    private static global::System.Globalization.CultureInfo resourceCulture;

    [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
    internal Resources() {
    }

    /// <summary>
    ///   Returns the cached ResourceManager instance used by this class.
    /// </summary>
    [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
    internal static CachedResourceManager ResourceManager 
    {
        get {
               if (object.ReferenceEquals(resourceMan, null))
               {
                  CachedResourceManager temp = new CachedResourceManager("Project.Properties.Resources", typeof(Resources).Assembly);
                  resourceMan = temp;
               }
               return resourceMan;
            }
    }

    // Image/object properties for your resources

} // End of resources class

这极大地减少了内存使用量,并极大地改善了新 Form 实例的加载时间。

于 2018-04-05T03:59:00.830 回答
4

每次您需要使用资源中的图像时,关于调用“ResourceManager.GetObject”的另一件事是它似乎每次都创建一个新的 Windows 句柄。在你的情况下可能没什么大不了的,但如果你像我们一样坚持一段时间,这可能会导致问题。

我们有一个 DataGridView,我们将资源中的图像推送到网格的不同字段中,当该网格超过 3000 行时,我们实际上超过了 32 位程序允许的最大 Windows 句柄。

错误出现随机参数异常,并显示消息“参数无效”。我们花了几个小时才想到我们有内存泄漏,但最终发现我们用那个网格加载了这个 GUI,应用程序句柄在它完成加载之前从 700-1000 到超过 10K,并且会使整个程序崩溃并且无法恢复。所以我在这里推荐选项2。

于 2013-05-23T16:04:56.443 回答
2

我还在我的课程中实现了“读取一次然后存储在变量中”的概念。

举个例子,这是我的代码的摘录:

internal static class MyResourcesHolder
{
    private static Image _i1;
    private static Image _i2;
    private static Image _i3;
    private static Image _i4;
    private static Image _i5;

    public static Image MyImage01 => _i1 ?? (_i1 = Resources.MyImage01);
    public static Image MyImage02 => _i2 ?? (_i2 = Resources.MyImage02);
    public static Image MyImage03 => _i3 ?? (_i3 = Resources.MyImage03);
    public static Image MyImage04 => _i4 ?? (_i4 = Resources.MyImage04);
    public static Image MyImage05 => _i5 ?? (_i5 = Resources.MyImage05);
}

也许有一天这会对某人有所帮助。

于 2015-11-18T12:17:39.723 回答
1

MSDN 文档指出资源的值由 ResourceManager.GetObject 返回。由于听起来各个位图在运行时不会改变,所以我认为接近 #2 的唯一缺点是您的内存占用会更大一些。

于 2010-10-29T02:32:33.950 回答