Search code examples
c#.netapplicationdomain

implement callback over ApplicationDomain-boundary in .net


I load a dll dynamically using an Applicationdomain to unload when nessesary. What i cant get to work is a callback-method from the created Appdomain if the task in the loaded dll terminates itself.

What i have so far

public interface IBootStrapper
{
    void AsyncStart();
    void StopAndWaitForCompletion();

    event EventHandler TerminatedItself;
}

and the "Starter" side

private static Procedure CreateDomainAndStartExecuting()
{
  AppDomain domain = AppDomain.CreateDomain("foo", null, CODEPATH, string.Empty, true);
  IBootStrapper strapper = (IBootStrapper)domain.CreateInstanceAndUnwrap(DYNAMIC_ASSEMBLY_NAME, CLASSNAME);
  strapper.ClosedItself += OnClosedItself;
  strapper.AsyncStart();

  return delegate
  {
      strapper.StopAndWaitForCompletion();
      AppDomain.Unload(domain);
  };
}

which results in a assembly not found exception because OnClosedItself() is a method of a type only known to the Starter, which is not present in the appdomain.

If I wrapp the OnClosedItself as delegate in a serializable class it's the same.

Any suggestions?

Edit: What I'm trying to do is building a selfupdating task. Therefor i created a starter, which can stop and recreate the task if a new version is available. But if the task is stopped from somewhere else, it should also notify the starter to terminate.

// stripped a lot of temporary code from the question

EDIT 2: Haplo pointed me to the right direction. I was able to implement the callback with semaphores.


Solution

  • I solved this situation by using a third assembly that had the shared type (in your case the implementation for IBoostrapper). In my case I had more types and logic, but for you it might be a bit overkill to have an assembly just for one type...

    Maybe you would prefer to use a shared named Mutex? Then you can synchronize the 2 AppDomains tasks...

    EDIT:

    You are creating the mutex on the main Appdomain, and also as initially owned, so it will never stop on WaitOne() beacuse you already own it.

    You can, for example, create the Mutex on the spawned Appdomain inside the IBootstrapper implementing class, as initially owned. After the CreateInstanceAndUnwrap call returns, the mutex should exist and it's owned by the Bootstrapper. So you can now open the mutex (call OpenExisting so you are sure that you're sharing it), and then you can WaitOne on it. Once the spawned AppDomain bootstrapper completes, you can Release the mutex, and the main Appdomain will complete the work.

    Mutexes are system wide, so they can be used across processes and AppDomains. Take a look on the remarks section of MSDN Mutex

    EDIT: If you cannot make it work with mutexes, see the next short example using semaphores. This is just to illustrate the concept, I'm not loading any additional assembly, etc.... The main thread in the default AppDomain will wait for the semaphore to be released from the spawned domain. Of course, if you don't want the main AppDomain to terminate, you should not allow the main thread to exit.

    class Program
    {
        static void Main(string[] args)
        {
            Semaphore semaphore = new Semaphore(0, 1, "SharedSemaphore");
            var domain = AppDomain.CreateDomain("Test");
    
            Action callOtherDomain = () =>
                {
                    domain.DoCallBack(Callback);
                };
            callOtherDomain.BeginInvoke(null, null);
            semaphore.WaitOne();
            // Once here, you should evaluate whether to exit the application, 
            //  or perform the task again (create new domain again?....)
        }
    
        static void Callback()
        {
            var sem = Semaphore.OpenExisting("SharedSemaphore");
            Thread.Sleep(10000);
            sem.Release();
        }
    }