0

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);
}
Boppity Bop
  • 9,613
  • 13
  • 72
  • 151
  • @JonasH There is no such thing as separate AppDomains in .NET Core or .NET 5+. `AssemblyLoadContext` can unload, but only if there are no references to any of its objects in the GC tree. – Charlieface Aug 24 '23 at 11:39
  • @Charlieface Good to know, I was not aware of AssemblyLoadContext. But reading its description it still seem difficult and fragile, and I would seriously consider using a process for isolation instead. – JonasH Aug 24 '23 at 11:48
  • Do not run the application with the debugger attached when you evaluate the `IsAlive` property of a `WeakReference`. You'll find more details about this [here](https://stackoverflow.com/questions/33317625/weakreference-behaving-differently-in-debug-and-release-no-debugger-attached/33318708#33318708). – mm8 Aug 24 '23 at 12:26
  • that link doesnt explain anything. it also makes no sense. if objects would behave differently dependent on build config then itd be a nightmare to develop in such frameworks.. anywho.. i just replaced Debug prints with logging and ran it standalone in Release and the behaviour is exactly the same... – Boppity Bop Aug 24 '23 at 12:56

0 Answers0