概述
我需要使用具有4个主电源功能的本机API进行绑定:
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。
Engine
对象充当单身,绑定到ActivateEngine
和TerminateEngine
。
Module
对象用于在Engine
中创建多个实例,并在原生API中绑定到CreateModule
和DestroyModule
。
遇到问题
我已经以某种方式实现了一些用户可以直接创建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) ...
当然这是不合适的订单。
问题
如何强制所有Module
在Engine
之前完成?
我真的不想强迫最终用户在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
来查看以下主题:
但我不明白这对我的情况有何帮助。
答案 0 :(得分:3)
针对您的特定情况提供更多解决方法,而不是解决一般问题:
分配在引擎单例和Module对象上终止引擎的义务。
创建一个通过Interlocked
methods(或本机等效项)更新的共享计数器。这可能是static volatile int
字段或非托管内存。
int
应计算应用维护引擎的'引用次数'。在每个构造函数中以原子方式递增计数器,并在每个终结器中递减它。将计数器减少到零的一个终结器也会调用TerminateEngine()
(并释放共享计数器)
如果用户允许所有Module
个对象进行垃圾回收,然后开始创建新模块,则Engine对象也必须计为“引用”。否则,发动机将被摧毁。
答案 1 :(得分:3)
我认为你不能按照你想要的方式接近它。最终确定的顺序是非确定性的,因此无法确定您的单身Engine
或Module
是否将首先完成。
我建议您删除引擎单例(如果您愿意,将引擎保留为静态类,但不允许任何实例,仅将其用于静态方法以执行引擎初始化和终止)以及模块的注册和使用一个静态原子计数器,沿着@ 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)