[PURPOSE]
I need to use reflections to
- Load an assembly into a separate ApplicationLoadContext
- Create a type from it
- Create an instance from the type
- Invoke functions on the instance repeatedly
- After some time, perhaps days, when this assembly is no longer needed, unload the ApplicationLoadContext then delete the assembly file.
If you want to know why I am doing this, please see skybridge.net.au.
[The problem]
Although I have set all references to the assembly, type and instances to null before unloading the ApplicationLoadContext, it still failed to unload.
[Code demo 1]
The following code shows how my code works. It failed to delete the DLL file:
namespace MyDll
{
public class MyText
{
public string m_text;
public MyText(string text)
{
m_text = text;
}
public string GetText()
{
return m_text;
}
}
public class MyClass
{
public string GetTextByBytes(byte[] bytes)
{
string json = Encoding.UTF8.GetString(bytes);
var obj = JsonConvert.DeserializeObject<MyText>(json);
return obj.GetText();
}
public string GetTextByObject(MyText text)
{
return text.GetText();
}
public string GetGuidText(Guid guid)
{
return guid.ToString();
}
public string GetText(string str)
{
return str;
}
public List<string> GetMultipleTexts(List<string> lst)
{
return lst;
}
}
}
static void Main(string[] args)
{
var ctx = new AssemblyLoadContext(null, isCollectible: true);
var assembly = ctx.LoadFromAssemblyPath(dllFilePath);
var myType = assembly.GetType("MyDll.MyClass");
var instance = Activator.CreateInstance(myType, new object[] { }, null);
var myMethod = myType.GetMethod("GetGuidText");
var res = myMethod.Invoke(instance, new object[] { Guid.NewGuid() });
assembly = null;
myType = null;
instance = null;
myMethod = null;
GC.Collect();
GC.WaitForPendingFinalizers();
Thread.Sleep(2000);
GC.Collect();
GC.WaitForPendingFinalizers();
ctx.Unload();
GC.Collect();
GC.WaitForPendingFinalizers();
Thread.Sleep(2000);
GC.Collect();
GC.WaitForPendingFinalizers();
File.Delete(dllFilePath);
Console.WriteLine($"Done!");
}
[Code demo 2]
But, if I put the code in a separate function, Test(), the deletion works:
private static void Test(AssemblyLoadContext ctx)
{
var assembly = ctx.LoadFromAssemblyPath(dllFilePath);
var myType = assembly.GetType("MyDll.MyClass");
var instance = Activator.CreateInstance(myType, new object[] { }, null);
var myMethod = myType.GetMethod("GetGuidText");
var res = myMethod.Invoke(instance, new object[] { Guid.NewGuid() });
}
static void Main(string[] args)
{
var ctx = new AssemblyLoadContext(null, isCollectible: true);
Test(ctx);
ctx.Unload();
GC.Collect();
GC.WaitForPendingFinalizers();
Thread.Sleep(2000);
GC.Collect();
GC.WaitForPendingFinalizers();
File.Delete(dllFilePath);
Console.WriteLine($"Done!");
}
[But I can't change my code to the demo 2 style]
In my SkyBridge app, the loaded assembly and the types and instances loaded from this assembly are placed into a global collection, to be invoked by a separate thread. So I can't put them into a function as local variables and let them go out of scope when that function exits, like in [Code demo 2].
Instead, when an assembly is no longer needed, all I can do is like [Code demo 1], to remove them from the global collection, set their references to null, and hope GC can collect them so I can unload and delete. But it always fails.
[Final Question]
Is there anything that I can do in [Code demo 1] to enable the unloading and deletion, without putting the types and instances into a function as local variables?
********* Added on 2023-06-02 **********
Now the problem becomes clearer. If a local variable is declared in a function, when that function ends, that variable goes out of scope. When I call GC.Collect, it is immediately collected. However, if this variable is a global variable that never goes out of scope, even if I set it to null then call GC.Collect, it is not collected.
Is there a way to force a global variable that never goes out of scope to be immediately collected after GC.Collect?