3

I've been working to get a windows service to automatically restart an app when there is a new version. My code is below, and it actually works. My question is this: Is there a big no-no in here somewhere? I'm new to creating app domains on separate threads, and I'd like to know if there is a more elegant way to accomplish this. I'm also concerned that maybe I have a big blind spot regarding how I'm handling this. Any advice is appreciated.

There are two general pieces: the Windows service that runs, starts my app in a different process, then polls to detect when a new version of that app is available. The second piece is the actual app that gets started, updated, restarted.

First, when the service starts, it calls LoadApp() so that the app starts running.

private void LoadApp()
    {
    // ** appDomainSetup here **

    this._appDomain = AppDomain.CreateDomain("MyUpdatedApp", AppDomain.CurrentDomain.Evidence, appDomainSetup);

    this._tokenSource = new CancellationTokenSource();

    Task.Factory.StartNew(() =>
    {
        try
        {
            this._appDomain.ExecuteAssembly(assembly);
        }
        catch (AppDomainUnloadedException ex)
        {
            Debug.WriteLine(ex.ToString());
        }
    }, _tokenSource.Token);
}

Then, every 10 seconds, the timer calls this method:

private void Process(object stateInfo)
{
    if (!Monitor.TryEnter(_locker)) { return; }

    try
    {
        // Check for new binaries. If new, copy the files and restart the app domain.            
        if (!NewAppVersionReady()) { return; }
        DeleteFiles();
        CopyFiles();
        RestartAppDomain();
    }
    finally
    {
        Monitor.Exit(_locker);
    }
}

RestartAppDomain() looks like this. Note that I get an exception when I unload the app domain on the second line in this method. (I get around that by catching the exception and basically ignoring it.)

private void RestartAppDomain()
{
    this._tokenSource.Cancel();
    AppDomain.Unload(this._appDomain);
    LoadApp();
}

Edit: I've since found out that the AppDomain.Unload() line is unnecessary. Without it, the service still updates the app with a new version and then starts the new version of the app. My big concern is calling Cancel() on the token source. I'd like the app to be able to shut down gracefully instead of just forcing it shut.

Bob Horn
  • 33,387
  • 34
  • 113
  • 219

1 Answers1

4

Through some trial and error, and learning, I seem to have settled on a decent solution. I thought I'd post it here in case it helps anyone else.

First, I created an interface, in a separate project, so the self-updating app won't have to reference the app that actually gets run in its own app domain. This interface was used so that the self-updating app can call Start and Stop on the other app.

public interface IStartStop
{
    void Start();
    void Stop();
}

In the app that gets run in a separate domain, I had the class derive from MarshalByRefObject, and implement IStartStop.

public class PrestoTaskRunnerController : MarshalByRefObject, IStartStop, IDisposable

This allows me to start and stop this app from my self-updating app:

this._appDomain = AppDomain.CreateDomain(this._appName, AppDomain.CurrentDomain.Evidence, appDomainSetup);
this._startStopControllerToRun = (IStartStop)this._appDomain.CreateInstanceFromAndUnwrap(assemblyName, this._fullyQualifiedClassName);
this._startStopControllerToRun.Start();

And stopping it is done this way:

this._startStopControllerToRun.Stop();
AppDomain.Unload(this._appDomain);

There are some more details, but this is the general concept, and it seems to be working well.

Bob Horn
  • 33,387
  • 34
  • 113
  • 219
  • Glad to hear you got it working! I am doing something very similar, so I would be interested in seeing more of your code/psuedocode to compare and maybe we could each learn something from each other. My main goal was to host Assemblies in a Database, but there are a few neat aspects that I managed to get working, such as avoiding the need for a common dependency (your IStartStop interface, an extra dll). – Thracx Jun 28 '13 at 20:54
  • How did you avoid the common dependency? – Bob Horn Jul 04 '13 at 23:56