强制终结者订购

时间:2014-01-07 13:22:52

标签: c# weak-references finalizer

概述

我需要使用具有4个主电源功能的本机API进行绑定:

void ActivateEngine();
int CreateModule();
void DestroyModule(int id);
void TerminateEngine();

文档指出,ActivateEngineTerminateEngine应围绕对CreateModuleDestroyModule的任何调用。用法应该是这样的:

void foo()
{
    ActivateEngine();

    int module1 = CreateModule();
    int module2 = CreateModule();

    ...

    DestroyModule(module2);
    DestroyModule(module1);

    TerminateEngine();
}

为此,我创建了两个.NET对象,即EngineModule,它们都使用DllImport属性绑定到本机API。

Engine对象充当单身,绑定到ActivateEngineTerminateEngine

Module对象用于在Engine中创建多个实例,并在原生API中绑定到CreateModuleDestroyModule

遇到问题

我已经以某种方式实现了一些用户可以直接创建Modules而无需过多关于Engine或关于对象生命周期的事情(即我没有 [和我]不希望] 强制用户在不再使用时处置对象。)

为此,我在WeakReference对象中使用了一个Engine列表,指向所有已创建的Modules

请参阅我的简化代码here

问题在于,当应用程序结束时,终结器以非确定方式调用,WeakReference目标已经为空,即使尚未调用Module的终结器且该参数trackResurrection设置为true。

在我的情况下,代码记录以下内容:

ActivateEngine() ...
CreateModule() ==> 0 ...
CreateModule() ==> 1 ...
DestroyModule(1) ...
  Ooops ... Can't dispose registered module because WeakReference to it is already null ...
  Ooops ... Can't dispose registered module because WeakReference to it is already null ...
TerminateEngine() ...
DestroyModule(0) ...

当然这是不合适的订单。

问题

如何强制所有ModuleEngine之前完成?

我真的不想强迫最终用户在Dispose个对象上调用Module方法,我也不想保留对创建的Module的强引用,以便对象当代码中不再引用时,它会自动消失。例如:

 processing
 {
     var module = new Module();
     ...
 }

 foo()
 {
     processing();
     GC.Collect(); // If using strong references 'module' is gonna be kept alive (that's not smart)  
 }

我已经使用ConditionalWeakTable来查看以下主题:

但我不明白这对我的情况有何帮助。

4 个答案:

答案 0 :(得分:3)

针对您的特定情况提供更多解决方法,而不是解决一般问题:

分配在引擎单例和Module对象上终止引擎的义务。

创建一个通过Interlocked methods(或本机等效项)更新的共享计数器。这可能是static volatile int字段或非托管内存。

int应计算应用维护引擎的'引用次数'。在每个构造函数中以原子方式递增计数器,并在每个终结器中递减它。将计数器减少到零的一个终结器也会调用TerminateEngine()(并释放共享计数器)

如果用户允许所有Module个对象进行垃圾回收,然后开始创建新模块,则Engine对象也必须计为“引用”。否则,发动机将被摧毁。

答案 1 :(得分:3)

我认为你不能按照你想要的方式接近它。最终确定的顺序是非确定性的,因此无法确定您的单身EngineModule是否将首先完成。

我建议您删除引擎单例(如果您愿意,将引擎保留为静态类,但不允许任何实例,仅将其用于静态方法以执行引擎初始化和终止)以及模块的注册和使用一个静态原子计数器,沿着@ Christian-Klauser的答案行(在Module构造函数中递增并在终结器中递减)。当原子计数器从0变为1时,您可以激活引擎(通过调用内部静态引擎方法),同样在模块计数变为0时终止引擎。

我还建议您在使用Module类时要求用户使用using机制。

答案 2 :(得分:2)

正如其他答案和评论所说,你需要实现某种形式的引用计数。这是我尝试这样做的(当你发布你的答案时我正在研究这个),它仍然使用单例Engine(现在没有要求这样做,你可以使它成为一个静态类,只需要很少的更改)但是,当计数分别达到1或0时,调用者需要调用AddRefrence()ReleaseRefrence()让引擎知道是否需要设置或拆除API。

Module支持在调用Dispose()或类完成时释放它的引用。

using System.Threading;

namespace FinalizerOrder
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;

    class Engine
    {
        private Engine()
        {
            //ActivateEngine() is no longer called here.
        }

        private readonly static Engine _singleton = new Engine(); //Now that the constructor is empty we can initialize immediately.
        private readonly static object _syncLock = new object();
        private static volatile int _counter = 0;

        public static Engine Singleton
        {
            get { return _singleton; }
        }

        public void AddRefrence()
        {
            lock (_syncLock)
            {
                _counter++;
                if (_counter < 0)
                    throw new InvalidOperationException("ReleaseRefrence() was called more times than AddRefrence()");

                if(_counter == 1)
                    Debug.WriteLine("ActivateEngine() ...");
            }
        }

        public void ReleaseRefrence()
        {
            lock (_syncLock)
            {
                _counter--;

                if (_counter < 0)
                    throw new InvalidOperationException("ReleaseRefrence() was called more times than AddRefrence()");

                if (_counter == 0)
                {
                    Debug.WriteLine("TerminateEngine() ...");
                }
            }
        }
    }

    class Module : IDisposable
    {
        public Module()
        {
            Engine.Singleton.AddRefrence();

            _id = _counter++;
            Debug.WriteLine("CreateModule() ==> {0} ...", _id);

        }

        private readonly int _id;
        private static int _counter;

        ~Module()
        {
            Dispose(false);   
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private bool _disposed = false;

        protected void Dispose(bool disposing)
        {
            if(_disposed)
                return;

            _disposed = true;                

            if (disposing)
            {
                //Nothing to do here, no IDisposeable objects.
            }

            Debug.WriteLine("DestroyModule({0}) ...", _id);
            Engine.Singleton.ReleaseRefrence();
        }
    }

    internal class Program
    {
        private static void Main()
        {
            Test();
            GC.Collect(3, GCCollectionMode.Forced);
            GC.WaitForPendingFinalizers();
            Test();

        }

        private static void Test()
        {
            var module1 = new Module();
            var module2 = new Module();

            GC.KeepAlive(module2);
            GC.KeepAlive(module1);
        }
    }
}

答案 3 :(得分:1)

我最终使用了FastSmartWeakEvent模式。

这是一般解决方案,易于阅读/理解。

请参阅更新的示例代码here