General overview
I need to bind with a native API that has 4 mains functions:
void ActivateEngine();
int CreateModule();
void DestroyModule(int id);
void TerminateEngine();
And the documentation states that ActivateEngine
and TerminateEngine
should surround any call to CreateModule
and DestroyModule
. That is usage should be something like:
void foo()
{
ActivateEngine();
int module1 = CreateModule();
int module2 = CreateModule();
...
DestroyModule(module2);
DestroyModule(module1);
TerminateEngine();
}
To do this I have created two .NET objects, namely Engine
and Module
both binded to the native API using the DllImport
attribute.
Engine
object acts as singleton and is binded to ActivateEngine
and TerminateEngine
.
Module
object is used to create many instances within the Engine
and is binded to CreateModule
and DestroyModule
in the native API.
Encountered issue
I have implemented things in a way that users can create Modules
directly without carrying too much about the Engine
or about the lifetime of the objects (i.e. I don't [And I don't want to] force users to dispose objects when no longer used).
To do this I have used a list of WeakReference
in Engine
object that points to all created Modules
.
See my simplified code here.
The issue is that when application's end, finalizer are called in non determistic way and WeakReference
targets are already null even if finalizer of Module
has not been called yet and that parameter trackResurrection
is set to true.
In my case the code logs the following:
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) ...
Which of course is inappropriate order.
Question
How can force all Module
to be finalized before the Engine
?
I truly don't want to force end-users to call Dispose
method on Module
objects and I also don't want to keep strong references to created Module
so that objects can disappear automatically when no longer referenced in the code. Example:
processing
{
var module = new Module();
...
}
foo()
{
processing();
GC.Collect(); // If using strong references 'module' is gonna be kept alive (that's not smart)
}
I have looked to following threads using ConditionalWeakTable
:
But I don't understand how this can help in my situation.
As the other answers and comments have said you need to implement some form of reference counting. Here is my attempt at doing it (I was working on this when you posted your answer), It still uses a singleton Engine
(there is no requirement now to do so, you could make it a static class with minimal changes), however callers need to call AddRefrence()
and ReleaseRefrence()
to let the engine know if it needs to setup or tear down the API when the count hits 1 or 0 respectively.
Module
supports releasing it's reference on calling Dispose()
or when the class is finalized.
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);
}
}
}