I am trying to establish for a fact whether my dynamically loaded dll has been unloaded.
My use case - there are user defined C# scripts that are used in a data grid columns in .NET 7 WPF app. A user can make changes and then compile new version to a different dll. At this point I need to unload previous version of that dll.
I seems to not able to unload it correctly. I watered down the test to the following:
I am using this as the basis for my test code: https://learn.microsoft.com/en-us/dotnet/standard/assembly/unloadability
It works in one case but breaks in another. I am not sure whether I am using it wrong or it didnt unload in second case.
DLL is loaded in custom AssemblyLoadContext
If I press button1 the RunTest
creates context, loads dll, evaluates and prints correct value, unloads, weak ref becomes not IsAlive.
However if I split RunTest to 2 stages - RunTest2
and Button2_Click
. In one - it creates context. In stage 2 (Button2) creates weak ref and unloads, then checks for weak ref, but it IsAlive forever.
Having said that if I look in VS "Modules" window - there is dll_2098706330365.dll
after button1 is clicked and then it vanishes after button2 is clicked.
So my question here - do I use weak ref incorrectly and dll is actually unloaded.. Or dll is not unloaded and my understanding of what Modules window shows is incorrect..
In either way - is there a bulletproof method to check whether the dll has been unloaded?
private void Button1_Click(object sender, RoutedEventArgs e)
{
var spo = model.GetDataObject(0);
RunTest(spo, @"dll_2098706330365.dll"); // THIS WORKS
RunTest2(spo, @"dll_2098706330365.dll"); // THIS DOES NOT
}
private void Button2_Click(object sender, RoutedEventArgs e)
{
WeakReference weakRef;
TestUnload22(alc, out weakRef);
TestUnload23(weakRef);
}
MyAssemblyLoader alc;
public void RunTest(DataObject spo, string path)
{
WeakReference weakRef;
TestUnload(path, spo, out weakRef);
for (int i = 0; weakRef.IsAlive && (i < 10); i++)
{
GC.Collect();
GC.WaitForPendingFinalizers();
Debug.WriteLine(" >>> UnloadTest: attemtp: {0} Weak ref {1}", i, weakRef.IsAlive);
}
Debug.WriteLine(">>> RunTest: ended {0}", weakRef.IsAlive);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void TestUnload(string path, DataObject spo, out WeakReference weakRef)
{
var alc = new MyAssemblyLoader (path);
Assembly a = alc.LoadFromAssemblyPath(path);
weakRef = new WeakReference(alc, trackResurrection: true);
var scriptInstance = InstantiateScript(a);
var result = scriptInstance.Evaluate(spo, null)?.ToString();
Debug.WriteLine(" >>> Evaluate: " + result);
alc.Unload();
}
public void RunTest2(ISPDataObject spo, string path)
{
TestUnload2(path, spo);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void TestUnload2(string path, ISPDataObject spo)
{
alc = new MyAssemblyLoader (path);
Assembly a = alc.LoadFromAssemblyPath(path);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void TestUnload22(MyAssemblyLoader alc, out WeakReference weakRef)
{
weakRef = new WeakReference(alc, trackResurrection: true);
alc.Unload();
}
private void TestUnload23(WeakReference weakRef)
{
for (int i = 0; weakRef.IsAlive && (i < 10); i++)
{
GC.Collect();
GC.WaitForPendingFinalizers();
Debug.WriteLine(" >>> UnloadTest2: attemtp: {0} Weak ref {1}", i, weakRef.IsAlive);
}
Debug.WriteLine(">>> RunTest2: ended {0}", weakRef.IsAlive);
}