一般概述
我需要绑定一个本地API,它有4个主要功能:
void ActivateEngine();
int CreateModule();
void DestroyModule(int id);
void TerminateEngine();
文档指出,ActivateEngine
和TerminateEngine
应该包围对CreateModule
和DestroyModule
的任何调用。这就是用法应该类似于:
void foo()
{
ActivateEngine();
int module1 = CreateModule();
int module2 = CreateModule();
...
DestroyModule(module2);
DestroyModule(module1);
TerminateEngine();
}
为此,我创建了两个.NET对象,即Engine
和Module
,它们都使用DllImport
属性绑定到本机API。
CCD_ 8对象充当singleton并绑定到CCD_ 9和CCD_。
Module
对象用于在Engine
内创建许多实例,并绑定到本地API中的CreateModule
和DestroyModule
。
遇到问题
我已经实现了这样一种方式,即用户可以直接创建Modules
,而无需携带太多关于Engine
或对象生存期的信息(即,我不想强制用户在不再使用对象时处理对象)。
为此,我在Engine
对象中使用了一个指向所有创建的Modules
的WeakReference
列表。
请在此处查看我的简化代码。
问题是,当应用程序结束时,以非决定性的方式调用终结器,并且即使尚未调用Module
的终结器并且参数trackResurrection
设置为true,WeakReference
目标也已经为null。
在我的情况下,代码记录如下:
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) ...
这当然是不恰当的顺序。
问题
如何强制所有Module
在Engine
之前完成?
我真的不想强迫最终用户在Module
对象上调用Dispose
方法,我也不想保留对创建的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
:查看了以下线程
- C#WeakReference对象在终结器中为NULL,尽管仍然被强引用
- 从ConditionalWeakTable获取活动项的列表
但我不明白这对我的处境有什么帮助。
更多的是针对特定情况的解决方案,而不是一般问题的解决方案:
在engine singleton和Module对象上分配终止引擎的义务。
创建通过Interlocked
方法(或本机等效方法)更新的共享计数器。这可能是一个static volatile int
字段或一段非托管内存。
int
应该计算应用程序维护的引擎的"引用"数量。在每个构造函数中原子递增计数器,在每个终结器中递减计数器。将计数器减少到零的一个终结器也调用TerminateEngine()
(并释放共享计数器)
Engine对象也必须算作"引用",以防用户让所有Module
对象都被垃圾收集,然后开始创建新模块。否则,发动机会被提前摧毁。
我认为您无法按照自己的意愿处理此问题。最终确定的顺序是不确定的,因此不可能知道您的单例Engine
还是Module
将首先最终确定。
我建议您删除Engine singleton(如果愿意,请将Engine保留为静态类,但不允许任何实例,只将其用于执行引擎初始化和终止的静态方法)和模块的注册,并使用静态原子计数器(在Module构造函数中递增,在终结器中递减)。当原子计数器从0变为1时,您可以激活引擎(通过调用内部静态引擎方法),并同样在模块计数变为0时终止引擎。
我还建议用户在使用Module类时使用using
机制。
正如其他答案和评论所说,您需要实现某种形式的引用计数。这是我的尝试(当你发布你的答案时,我正在做这方面的工作),它仍然使用一个单例Engine
(现在没有要求这样做,你可以使它成为一个静态类,只做最小的更改),但是调用方需要调用AddRefrence()
和ReleaseRefrence()
,让引擎知道当计数分别达到1或0时,它是否需要设置或删除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);
}
}
}
我最终使用了FastSmartWeakEvent模式。
这是一个通用的解决方案,易于阅读/理解。
请参阅此处的更新示例代码。