1

我的应用程序中的库需要全局使用非托管资源。

为了促进这一点,图书馆有以下内容:

  • 有一个单例类继承自IDisposable.
  • 非托管资源的获取发生在Instance()类的方法中。
  • 资源将始终通过MySingleton对象访问,因此这似乎是确保非托管资源在需要时可用的合乎逻辑的方式。

    public class MySingleton : IDisposable
    {
        private static MySingleton instance;
    
        public static MySingleton Instance
        {
            get
            {
                if (instance == null)
                {
                    lock (typeof(MySingleton))
                    {
                        if (instance == null)
                        {
                            instance = new MySingleton();
    
                            // Acquire unmanaged resource here
                        }
                    }
                }
    
                return instance;
            }
        }
    
        public void Dispose()
        {
            // Release unmanaged resource here
        }
    }
    

    问题

    • 具有上述单例的库被多个应用程序使用。
    • 为了确保正确清理非托管资源,我需要MySingleton.Instance.Dispose()在每个应用程序中调用(通常在一个finally块中以确保即使在所有情况下也会发生这种exception情况)。
    • 如果创建了另一个应用程序(即入口点)并且作者忘记调用该Dispose方法,这可能会导致未正确清理非托管资源。

    我尝试在MySingleton类中添加一个析构函数来执行此操作,但是在应用程序退出时似乎没有命中其中的断点。我猜这是因为 GC 是不确定的,也许应用程序在静态对象的析构函数被销毁之前就结束了,但我不确定。

    是否有一种优雅的方法来确保始终清理此资源,而不依赖于客户端应用程序显式调用Dispose以确保发生这种情况?

  • 4

    3 回答 3

    2

    无论其他人是否实际使用它,您对单例的静态引用都会使其保持活动状态。如果您希望检测对您的单例的所有外部引用何时消失而您自己却没有保持它活着,您必须执行以下操作之一:

    1. 为单例保留一个 `WeakReference`,并在每次有人请求对象实例时返回它。您可能希望同时保留一个长短的 `WeakReference`;一旦对象符合最终确定的条件,短的就会失效;一个 long 将保持有效,直到对象完成后的 GC 周期。如果一个短的 `WeakReference` 失效,即使你有一个很长的弱引用并且终结器尚未运行,也可能需要创建一个新实例。另一方面,您可能需要一个长的 `WeakReference` 来帮助处理在旧对象上请求终结器的时间和它实际运行的时间之间请求新对象的情况。
    2. 保持对“真实”单例的强烈引用,但永远不要将它暴露给任何人。相反,创建一个包装器并将一个 `WeakReference` 存储到它。包装器的终结器应要求单例自行清理。这种方法可能比第一种方法更安全,在这种情况下,代码在旧对象有资格进行最终确定和最终确定实际发生的时间之间请求单例实例。如果所有清理都通过一个 `Finalize` 方法发生,那么 get-singleton 方法可能会请求终结器跳过清理,并知道它是否及时发出请求(如果没有,它可能在创建新实例之前必须等待终结器完成)。
    3. 保持对“真实”单例的强烈引用,并为每个需要它的不同实体提供不同的包装器。保留一个尚未调用 `finalize` 的包装器的列表(最多保留对包装器本身的弱引用,但保留对其信息的强引用),当列表为空时,执行清理。这种方法往往比第二种方法更复杂,但它可以确定谁持有对您的对象的引用。

    如果您的资源是在旧实例存在时无法重新创建的资源,我的建议通常是方法#2。方法 #1 的一个问题是,如果有多个可终结对象与一个资源相关联,则可能会在相当长的时间间隔内部分清理资源,因此代码可能别无选择,只能等待其他对象' 终结者来完成。使用方法 #2 时,如果在终结器挂起时请求单例,则请求将很快到来以防止终结器执行清理(在这种情况下,它不需要等待终结器运行,因为终结器不会t 实际上做任何事情),它将在终结器运行之后出现(在这种情况下,可以立即创建新实例),

    于 2012-07-13T15:42:49.723 回答
    1

    让单例实现 IDisposable 听起来是错误的。谁负责处置该物品?

    当两个线程或两个进程尝试访问资源时应该发生什么?失败者应该阻止还是抛出异常?

    我认为您最好采用 RAII 方法,其中您有一个实现 IDisposable 的类,并且仅在资源使用时才存在:

    class MyResource : IDisposable { 
      public MyResource() {
        // Acquire resource here
      }
    
      public void Dispose() {
        // Free resource here, along with extra stuff to attempt
        // to catch situations where its not disposed 
      }
    }
    

    然后:

    using( var r = new MyResource() ) {
      // Do work here.
    }
    

    您不能保证使用您的库的所有其他开发人员都会记得正确处理该类,但是任何未能在 using 语句中使用该类的人都可以用一根大尖棒进行教育。

    于 2012-07-12T07:17:24.633 回答
    0

    关于你的析构函数没有被击中,看这里:http: //msdn.microsoft.com/en-us/library/66x5fx1b.aspx 析构函数被调用,但我认为你在调试时看不到。至于你的单例,很明显它在程序退出之前不会被调用,因为它是静态的。

    关于确保调用 Dispose,我认为您能做的最好的事情就是继承 IDisposable 并记录处置的需要。如果有一种方法可以自动处理事情,你不认为 MS 会对 SqlConnection 或任何其他一次性类做到这一点吗?顺便说一句,我认为确定何时将实例放置在类本身中并不是一个好习惯。这样你就限制了你的类的使用,并且你不知道将使用你的类的其他组件想要做什么。

    于 2012-07-12T06:57:26.243 回答