1

I am working on a project where I have to load assemblies (lets call them tasks) at runtime, run the task and then be able to kill off the whole assembly so that the dll can be replaced without interrupting the main application.

There are many of these tasks running within the main application, some run sequentially and some run in parallel. Occasionally one or to of these tasks need to be updated and then re-added to the queue. Currently we are stopping the entire application and interrupting the other tasks at whatever stage they are at, which is not ideal.

I have figured out that I will have to load each assembly into a separate AppDomain, but loading the assemblies in those domains is proving difficult, especially when I need to actually run the tasks and receive events from them. I have been looking into this problem for a couple of days and have still not managed to get a working proof-of-concept.

I have an Interface for the tasks which includes a 'run' and 'kill' method (subs) and a 'taskstep', 'complete' and 'killed' event. 'taskstep' returns an object to be cached later, 'complete' fires when the whole task is done and 'killed' fires when it is ready to be unloaded. There should also be a timeout on the whole process of two hours and a timeout of 2 minutes on the killed event in case it gets stuck, at which point I would like to be able to unload it, forcing any threads to terminate (which is what 'kill' should do anyway). Each assembly may contain several tasks, all of which should loadable be unloadable.

I have no problems loading these tasks as 'plugins' but am lost when trying to both use them and unload them. If I have to create some elaborate wrapper then so be it but is what I need even possible?

I have tried inheriting from MarshalByRefObject but I do not even know the assembly fullname unless I load it first, which then locks the file. I have tried loading from a byte array of the assembly. This means that the file is not locked but a copy of it remains in the current appdomain. This will become problematic over the following months / years!

For Each key As String In assemblies.Keys

    Dim ad As AppDomain = AppDomainHelper.BuildChildAppDomain(AppDomain.CurrentDomain, key)

    AddHandler ad.AssemblyResolve, AddressOf AssemblyResolve

    _l.Add(ad)

    For Each value As String In assemblies(key)
        Dim item As IScanner = CType(ad.CreateInstanceAndUnwrap(key, value), IScanner)
        ListBox1.Items.Add(item)
    Next
Next

Private Function AssemblyResolve(sender As Object, args As ResolveEventArgs) As Assembly
    Return GetType(IScanner).Assembly
End Function
Alex Essilfie
  • 12,339
  • 9
  • 70
  • 108
Amit Pore
  • 127
  • 3
  • 13
  • And what seems to be the problem with loading each assembly in its own appdomain? This is the approach I have taken in the past. – Sam Axe Dec 01 '11 at 16:38
  • I dont know the Assembly.fullname until runtime without loading it first.how can i load the assembly into its own appdomain when I only know its file path , thanks for the reply. – Amit Pore Dec 01 '11 at 16:46
  • System.Reflection.AssemblyName.GetAssemblyName(path) – Sam Axe Dec 01 '11 at 17:03
  • Now I have the assembly names (Thanks Boo) but I am struggling to get the scanners loaded (see my edited code). The Assembly fails to resolve "ResolveEventArgs are not serializable" – Amit Pore Dec 01 '11 at 17:10
  • It appears that exception is thrown from the new (secondary, if you will) AppDomain. Which means that the ResolveEventArgs have to be serialize to cross appdomain boundary. You should handle the event from within the secondary appdomain so you dont have to cross that boundary. – Sam Axe Dec 01 '11 at 22:47

2 Answers2

1

Consider using Managed Extensibility Framework.

It's part of .Net 4.0, and you can read a short overview here.

Loading and unloading assemblies can be a very bad idea - things doesn't work as your might expect (ever try to catch an exception thrown in another AppDomain? BAM you can't!)

Ohad
  • 2,752
  • 17
  • 15
0

It appears that exception is thrown from the new (secondary, if you will) AppDomain. Which means that the ResolveEventArgs have to be serialize to cross appdomain boundary. You should handle the event from within the secondary appdomain so you dont have to cross that boundary.

so... create a class that handles the resolutions.. something like...

Public Class AssemblyResolver
  Inherits MarshalByRefObject

  Public Property SearchPath As String = String.Empty

  Public Function ResolveAssembly(sender as Object, e As ResolveEventArgs) As Assembly

    Dim tPath As String = Path.Combine(SearchPath, e.Name & ".dll")

    If File.Exists(tPath) Then
      Return Assembly.LoadFrom(tPath)
    End If

    Return Nothing

  End Function

End Class

Now use it from your plugin loader...

Dim tPluginDomain As AppDomain = AppDomainHelper.BuildChildAppDomain(AppDomain.CurrentDomain, key)
Dim tResolver As AssemblyResolver = tPluginDomain.CreateInstanceAndUnwrap(GetType(AssemblyResolver).Assembly.FullName, GetType(AssemblyResolver).FullName)
AddHandler tPluginDomain.AssemblyResolve, AddressOf tResolver.ResolveAssembly

That should get you pointed down the right road.
All code was off the top of my head with reference to MSDN. DOnt know if it will compile.. expect to typo/debug it.

EDIT Whoops.. forgot to tag the resolver into the plugin domain. Fixed.

Sam Axe
  • 33,313
  • 9
  • 55
  • 89