Hi all,
I’m wondering if anyone has found an elegant way to unsubscribe from events in the C# script component when it is overwritten/modified. Currently, running the code (which subscribes to events), modifying the code and then running it again means we have two Script_Instance subscribed to those events, except only one of them is associated with the Grasshopper document.
This is a minor bump of this topic on the old forum.
Ideally, we would have something equivalent to a Finalizer/Destructor that executes when the component is overwritten or intended to be no longer referenced. An actual finalizer looks like:
private void RunScript(object x, object y, ref object A)
{
Rhino.RhinoApp.WriteLine("Script_Instance awake");
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
Rhino.RhinoApp.WriteLine("Script_Instance execution here...");
}
// <Custom additional code>
~Script_Instance()
{
Rhino.RhinoApp.WriteLine("Script_Instance destroyed");
}
// </Custom additional code>
But the issue is that it’s not going to get picked up by GC when it’s subscribed to events outside of itself (i.e. document events, 3rd party events).
So the question is, what’s the best (and ideally elegant…) way of knowing when a script instance is no longer in use, so we can unsubscribe any events and dispose of it properly?
One ‘solution’ to this is the following, but I’d rather have a more elegant (and not necessarily .dll dependent) solution for it:
All we do here is check a static container to see if the new script instance assigned to the component matches the old script instance, and if required execute the new constructor and the old destructor.
public static class GHUtilities
{
private static Dictionary<Guid, Tuple<GH_ScriptInstance, Action>> _scriptInstanceAssignments = new Dictionary<Guid, Tuple<GH_ScriptInstance, Action>>();
public static void LifeCycle(this GH_ScriptInstance instance, IGH_Component component, Action constructor, Action destructor)
{
lock (_scriptInstanceAssignments)
{
if (_scriptInstanceAssignments.TryGetValue(component.InstanceGuid, out Tuple<GH_ScriptInstance, Action> oldConfig))
{
if (oldConfig?.Item1 != instance)
{
oldConfig?.Item2();
constructor();
_scriptInstanceAssignments[component.InstanceGuid] = new Tuple<GH_ScriptInstance, Action>(instance, destructor);
}
}
else
{
constructor();
_scriptInstanceAssignments[component.InstanceGuid] = new Tuple<GH_ScriptInstance, Action>(instance, destructor);
}
}
}
}
And then usage is:
public override void AfterRunScript()
{
this.LifeCycle(Component,
() => {
Rhino.RhinoApp.WriteLine("Constructor");
// Subscribe to events here
}, () => {
Rhino.RhinoApp.WriteLine("Destructor");
// Unsubscribe from events here
});
}
Notes:
- Usage of AfterRunScript() rather than BeforeRunScript() because on first run this.Component is not assigned.
- This is a POC not production ready, as it doesn’t handle changing of instance guids, and should also watch for the component not being required further (i.e. monitoring objectsdeleted, documentclosed etc. and removing references to allow the script instances to be cleaned up)
Looking through the Reflect() of the script instance and container component didn’t appear to offer any event or overridable function when the script instance is changed.