How to: Running multiple WPF applications in the same process using AppDomains

June 19th, 2008

    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 Starup.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:


2 Responses to “How to: Running multiple WPF applications in the same process using AppDomains”

  1. JY Says:

    in WinForms it’s possible to get Forms on their own threads simply by calling Application.Run from within that new thread’s ThreadStart delegate.
    have you found a similar technique in WPF? the application i work on has many forms running on their own thread so is it viable to create an AppDomain for 10+ forms?

  2. Eugene Prystupa Says:

    Creating a separate app domain is an overkill for your scenario. Please see my other post at http://eprystupa.wordpress.com/2008/07/28/running-wpf-application-with-multiple-ui-threads/ for a better approach.