Running multiple WPF applications in the same process using AppDomains


Why?

There are various reasons you may want to run several full-blown independent WPF applications side-by-side in the same process. In my particular case this was a client project where we were building a composite WPF trading desk. One of the requirements was to be able to run different versions of the same component (montage, P&L, blotter, etc.) side-by-side but within seemingly integrated environment. The best solution turned out to be running different versions in completely independent environment (application) and only communicate across domains using a thin protocol to provide “integrated” look and feel. Running components in separate domains let us avoid all the problems that come with signing, versioning, evolving dependencies, and many more.

This post is a step by step guide to demonstrate the techniques involved.

Step 1: Creating basic WPF application

The first step is to create a simple host application. Select “File/New/Project…” in Visual Studio and pick “Visual C#/Windows/WPF Application”. Give it a name of “WPFDomainLab” and click OK.
The default application with a single main window is generated. Now go to the properties for “App.xaml” and change its “Build Action” from “Application Definition” to “Page”. This allows us to add our own Main() method with explicit WPF application startup code. If you try to compile at this point you should be getting “Program ‘XXXX.exe’ does not contain a static ‘Main’ method suitable for an entry point”.

We now need to add Main method to handle WPF startup. The following class will do it:

using System;

namespace WPFDomainLab
{
    class Startup
    {
        [STAThread()]
        static void Main()
        {
            App app = new App();
            app.MainWindow = new Window1();
            app.MainWindow.Show();
            app.Run();
        }
    }
}

Save this as Startup.cs and include it in your project. Now the project should compile. Before running it open Window1.xaml and have it display something interesting:

<Window x:Class="WPFDomainLab.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="100" Width="300">
    <Grid>
        <ContentControl
            Content="{Binding DomainName}"/>
    </Grid>
</Window>

This will display the current domain’s friendly name. We now need to open the code-behind file (Window1.xaml.cs) and add the code to provide binding data context:

using System;
using System.Windows;

namespace WPFDomainLab
{
  /// <summary>
  /// Interaction logic for Window1.xaml
  /// </summary>
  public partial class Window1 : Window
  {
    public Window1()
    {
      InitializeComponent();
      this.DataContext = new {
        DomainName = 
          "I live in " +
          AppDomain.CurrentDomain.FriendlyName
      };
    }
  }
}

If you compile and run the app at this point you should get the following window:


image

At this point we have a single WPF application running in the default application domain.

Step 2: Moving WPF app into a dedicated domain

Lets go ahead and move this application to its own dedicated domain. These are the changes needed to accomplish this in a simple way (Startup.cs):

1:  using System;   
2:      
3:  namespace WPFDomainLab   
4:  {   
5:    class Startup   
6:    {   
7:      [STAThread()]   
8:      static void Main()   
9:      {  
10:        AppDomain domain =  
11:          AppDomain.CreateDomain(  
12:            "another domain");  
13:        CrossAppDomainDelegate action = () =>  
14:        {  
15:            App app = new App();  
16:            app.MainWindow = new Window1();  
17:            app.MainWindow.Show();  
18:            app.Run();  
19:        };  
20:        domain.DoCallBack(action);  
21:      }  
22:    }  
23:  }

Lines 10-12 create another domain.

Lines 13-19 are used to package application startup logic in a delegate that will be remotely executed in another domain.

Line 20 actually runs the delegate in “another domain” and creates full-blown WPF application there. If you compile and run the app now you should get the following:



image
Step 3: Starting second WPF application in its own domain

Adding a second instance of our WPF application running in yet another domain looks simple now, but it has some caveats. Consider the following changes to create two separate domains each running a WPF app (Startup.cs):

1:  using System;   
2:      
3:  namespace WPFDomainLab   
4:  {   
5:    class Startup   
6:    {   
7:      [STAThread()]   
8:      static void Main()   
9:      {  
10:        var domain1 = AppDomain.CreateDomain(  
11:          "first dedicated domain");  
12:        var domain2 = AppDomain.CreateDomain(  
13:          "second dedicated domain");  
14:     
15:        CrossAppDomainDelegate action = () =>  
16:        {  
17:          App app = new App();  
18:          app.MainWindow = new Window1();  
19:          app.MainWindow.Show();  
20:          app.Run();  
21:        };  
22:     
23:        domain1.DoCallBack(action);  
24:        domain2.DoCallBack(action);  
25:      }  
26:    }  
27:  }

Lines 10-13 create two different domains.

Lines 23-24 actually create WPF applications in respective domains.

If you run host application at this point you will find that it doesn’t quite work as expected. First a single window appears stating “I live in ‘first dedicated domain’ domain”. Only when you close the first window, the second one appears (after a pause), now stating “I live in ‘second dedicated domain’ domain”.

The reason for this strange behavior is that all three domains (default one created by CLR and two domains we explicitly created) share the same execution thread. The first WPF app “hijacks” Window message pump and blocks the thread at “app.Run()”, so “domain2.DoCallBack(action);” is not even executed until the first app terminates.

Step 4: Starting a dedicated thread for each domain

To remedy this we need to create a dedicated thread for each of the domains that we create. The following changes accomplish the task (Startup.cs):

1:  using System;   
2:  using System.Threading;   
3:      
4:  namespace WPFDomainLab   
5:  {   
6:    class Startup   
7:    {   
8:      [STAThread()]   
9:      static void Main()  
10:      {  
11:        var domain1 = AppDomain.CreateDomain(  
12:          "first dedicated domain");  
13:        var domain2 = AppDomain.CreateDomain(  
14:          "second dedicated domain");  
15:     
16:        CrossAppDomainDelegate action = () =>  
17:        {  
18:          Thread thread = new Thread(() =>  
19:          {  
20:            App app = new App();  
21:            app.MainWindow = new Window1();  
22:            app.MainWindow.Show();  
23:            app.Run();  
24:          });  
25:          thread.SetApartmentState(  
26:            ApartmentState.STA);  
27:          thread.Start();  
28:        };  
29:     
30:        domain1.DoCallBack(action);  
31:        domain2.DoCallBack(action);  
32:      }  
33:    }  
34:  }

Lines 18-24 create a dedicated thread object to run WPF app and package WPF app startup code inside this thread’s start method.

Lines 25-26 sets the thread’s apartment to STA, this is a WPF requirement, otherwise it will not run (it will throw).

Lines 27 actually starts a dedicated thread in the given domain.

If you run application host now you will see both windows running in the same process in two different domains happily coexisting side by side. Applications are fully isolated, each getting its own static variables, base directory and configuration file (if customized). The performance is sluggish though and we are going to address this next.

Step 5: Optimizing assembly loading

When you ran the final application from step 4 you probably noticed that its startup time is extremely bad. Depending on your system it may take up to 10 seconds for the second application UI to show up.

The reason is the default .Net loader behavior. By default every domain gets its own copy of assemblies loaded into domain’s context independently of other domains. It means that all .Net Framework and WPF assemblies will need to be loaded and JIT-ed twice (once in each domain). In addition to duplicating the work loading assemblies in non-default domain is extremely slow (I haven’t figured out why). Fortunately there is a simple solution for the problem and a simple change will do it:

1:  using System;   
2:  using System.Threading;   
3:      
4:  namespace WPFDomainLab   
5:  {   
6:    class Startup   
7:    {   
8:      [STAThread()]   
9:      [LoaderOptimization(  
10:        LoaderOptimization.MultiDomainHost)]  
11:      static void Main()  
12:      {  
13:        ...  
14:      }  
15:    }  
16:  }

The attribute on line 9 does the trick. Basically it instructs .Net loader to load all GAC-installed assemblies in a domain-neutral way, thus sharing the assembly code between domains. This avoids loading and JIT-ing assemblies multiple times.

You can read more about domain-neutral assemblies here.

The final solution can be downloaded from here.

7 comments
  1. David B said:

    Have you figured out the ability to have WPF App’s UI Thread 1 (User Window 1) communicating with UI Thread 2 (User Window 2)….ie so the first user window can be sending UI elements to Window 2..both of which are running under the same WPF app, the same process ID, but two UI threads communicating UI elements?

    • Poweryang1 said:

      Use Dispatcher Object!

      The different UI threads can’t communicate with each other.

      So, you must Dispatcher object belonging to it’s own thread.

      For Example…

      // anotherWindow is a Window Object in another UI thread.

      this.anotherWindow.Dispatcher.BeginInvoke(
      () =>
      {
      this.anotherWindow.textblock.Text = “Hello, Another UI Thread!”;
      }
      );

      I’m Korean who isn’t proficient in English syntax.

  2. Cedric B said:

    Eugene, great posts. These are awesome.

    I am curious to know the answer to David B’s question as well. I would like to pass some UI elements to the 2nd UI thread, have the 2nd UI thread re-render the element with some modifications, and then pass the “new” element back to thread 1, so that thread 1 can put it into its display.

  3. Yury said:

    Warning though, it will not allow user input, so it sucks big time but at least it lets you crossaccess UI Threads.

  4. ross said:

    Hi. Thanks for the article, I found it quite useful.

    I have been experimenting with some ideas around this, and in one scenario I would like to Unload one of the domains, eg “second dedicated domain” from the startup AppDomain, whilst it is still running (eg the GUI is still showing).

    No matter how I try to do this, I keep getting an error

    “Error while unloading appdomain. (Exception from HRESULT: 0x80131015)”

    Do you know if there is a reason I can’t Unload() an AppDomain that is running a WPF App?

  5. Mohamed said:

    Is it possible to include two different applications in a WPF application as a wrapper for their exes? I am trying to have an MDI wrapper to implement this functionality but having some problems. The key over here is resembling web parts concepts.

Leave a comment