.Net的AppDomain

 

一、AppDomain的作用

 

AppDomain(应用程序域)是一种在.NET 中提供的隔离机制,用于将应用程序的不同部分隔离开来,以提高安全性、可靠性和可管理性。以下是其主要的功能和用途:

 

    1. 隔离性:
      • 允许在一个进程中运行多个应用程序,同时使它们相互隔离。这样可以避免一个应用程序中的错误影响到其他应用程序,即使它们在同一个进程中运行。例如,在一个服务器进程中,可以运行多个不同的 Web 应用程序,它们可以在各自的 AppDomain 中运行,避免相互干扰。
      • 每个 AppDomain 都有自己的加载程序集、配置和资源,这有助于防止不同应用程序之间的冲突。
    2. 资源管理:
      • 可以对应用程序的资源进行独立的管理,包括内存的分配和使用。当一个 AppDomain 不再需要时,可以将其卸载,从而释放其所占用的资源,包括内存和其他系统资源。
      • 有助于减少内存泄漏的风险,因为可以将不再使用的 AppDomain 及其相关资源清除。
    3. 安全性:
      • 不同的 AppDomain 可以有不同的安全权限。这意味着可以对不同的 AppDomain 进行不同的安全设置,比如允许一个 AppDomain 访问网络资源,而另一个不允许,从而提高系统的整体安全性。
      • 可以为 AppDomain 中的代码设置代码访问安全策略,限制代码的执行权限,确保代码不会执行未经授权的操作。
    4. 程序集加载和卸载:
      • 可以在不同的 AppDomain 中加载和卸载程序集,这提供了一种灵活的方式来管理应用程序的动态行为。
      • 允许在不影响其他 AppDomain 的情况下,更新或替换某个 AppDomain 中的程序集,这在需要动态更新程序的情况下非常有用,比如热更新应用程序的某些部分。
    5. 应用程序配置的独立性:
      • 每个 AppDomain 可以有自己的配置信息,如配置文件(app.config)。这使得不同的应用程序可以根据自己的需要设置不同的配置,即使它们在同一个进程中运行。

 

二、使用AppDomain在一个进程创建两个应用

 

  • 在 Main 函数中:
    • 首先,使用 AppDomain.CreateDomain 创建了两个 AppDomain,分别命名为 "FirstAppDomain" 和 "SecondAppDomain"。
    • 然后,创建了两个 Thread 对象 firstThread 和 secondThread,每个线程都将在其自己的 AppDomain 中运行一个应用程序。
    • 通过 new Thread(() => RunAppInDomain(domain, appType)) 为每个线程指定要执行的操作,即调用 RunAppInDomain 方法并传递相应的 AppDomain 和应用程序类型。
    • 使用 firstThread.Start() 和 secondThread.Start() 启动两个线程,这样它们会同时开始执行。
    • 使用 firstThread.Join() 和 secondThread.Join() 等待两个线程完成。这样可以确保在卸载 AppDomain 之前,线程中的操作已经完成。
    • 最后,使用 AppDomain.Unload 卸载两个 AppDomain。
  • 在 RunAppInDomain 方法中:
    • 使用 domain.CreateInstanceAndUnwrap 在指定的 AppDomain 中创建对象实例。
    • 通过 appType.GetMethod("Run") 找到要调用的 Run 方法。
    • 使用 method.Invoke 调用 Run 方法。
  • 在 FirstApp 和 SecondApp 类中:
    • Run 方法中包含了打印信息和 Thread.Sleep 模拟应用程序的运行时间,你可以添加更复杂的逻辑。

 

这个示例允许两个应用程序在不同的 AppDomain 中同时运行,每个应用程序都有自己的资源和执行环境,并且它们不会相互干扰。通过将每个应用程序的执行放在单独的线程中,实现了并行处理。

 

需要注意的是:

 

    • 当使用多线程时,要注意线程之间的同步和资源共享问题。在这个示例中,由于两个应用程序是隔离的,通常不会出现资源共享问题,但在更复杂的场景中,可能需要考虑线程安全。
    • 卸载 AppDomain 时要确保其中的操作已经完成,否则可能会导致异常。使用 Join 方法可以帮助我们等待线程完成操作。
    • 你可以根据实际需要修改 FirstApp 和 SecondApp 中的 Run 方法,添加更多的功能和逻辑。
复制代码
using System;
using System.Reflection;
using System.Threading;
using System.Windows.Forms;

namespace AppDomainTest
{
    internal class Program
    {
        static void Main(string[] args)
        {
            // 创建第一个 AppDomain
            AppDomain firstDomain = AppDomain.CreateDomain("FirstAppDomain");
            // 创建第二个 AppDomain
            AppDomain secondDomain = AppDomain.CreateDomain("SecondAppDomain");

            // 创建线程来运行第一个应用程序
            Thread firstThread = new Thread(() =>
            {
                RunAppInDomain(firstDomain, typeof(FirstApp));
            });
            // 创建线程来运行第二个应用程序
            Thread secondThread = new Thread(() =>
            {
                RunAppInDomain(secondDomain, typeof(SecondApp));
            });

            // 启动线程
            firstThread.Start();
            secondThread.Start();

            // 等待线程完成
            firstThread.Join();
            secondThread.Join();

            // 卸载 AppDomain
            AppDomain.Unload(firstDomain);
            AppDomain.Unload(secondDomain);

        }

        static void RunAppInDomain(AppDomain domain, Type appType)
        {
            // 在指定的 AppDomain 中创建对象实例
            object instance = domain.CreateInstanceAndUnwrap(
                appType.Assembly.FullName,
                appType.FullName);

            // 调用对象的方法
            MethodInfo method = appType.GetMethod("Run");
            if (method != null)
            {
                method.Invoke(instance, null);
            }
        }



    }

    [Serializable]
    class FirstApp
    {
        public void Run()
        {
            // 创建一个 Windows 窗体应用程序
            Form form = new Form();
            form.Text = "First App";
            form.Size = new System.Drawing.Size(300, 200);
            form.Load += Form_Load;
            Application.Run(form);
        }

        private void Form_Load(object sender, EventArgs e)
        {
            // 窗体加载时的操作
            MessageBox.Show("第一个应用的窗体已加载");
            // 可以添加更多逻辑
        }
    }

    [Serializable]
    class SecondApp
    {
        public void Run()
        {
            // 创建一个 Windows 窗体应用程序
            Form form = new Form();
            form.Text = "Second App";
            form.Size = new System.Drawing.Size(300, 200);
            form.Load += Form_Load;
            Application.Run(form);
        }

        private void Form_Load(object sender, EventArgs e)
        {
            // 窗体加载时的操作
            MessageBox.Show("第二个应用的窗体已加载");
            // 可以添加更多逻辑
        }
    }


}
复制代码

 

三、AppDomain的其他用法

1、程序集加载和卸载的更多细节

 

  1. 加载特定版本的程序集:
    • 可以在不同的 AppDomain 中加载特定版本的程序集,避免不同版本的程序集冲突。例如:

    收起
    csharp
     
    using System;
    using System.Reflection;
    
    class Program
    {
        static void Main()
        {
            AppDomain domain = AppDomain.CreateDomain("MyDomain");
            try
            {
                domain.Load("MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
                // 在这里可以使用在该 AppDomain 中加载的程序集
            }
            finally
            {
                AppDomain.Unload(domain);
            }
        }
    }
    
     

    • 上述代码使用 domain.Load 方法精确指定了要加载的程序集的版本号、区域性和公钥令牌,确保在该 AppDomain 中加载的是所需的特定版本程序集。
  2. 动态加载程序集并执行代码:
    收起
    csharp
     
    using System;
    using System.Reflection;
    
    class Program
    {
        static void Main()
        {
            AppDomain domain = AppDomain.CreateDomain("DynamicDomain");
            try
            {
                domain.AssemblyResolve += Domain_AssemblyResolve;
                object instance = domain.CreateInstanceFromAndUnwrap("MyAssembly.dll", "MyNamespace.MyClass");
                MethodInfo method = instance.GetType().GetMethod("MyMethod");
                if (method!= null)
                {
                    method.Invoke(instance, null);
                }
            }
            finally
            {
                AppDomain.Unload(domain);
            }
        }
    
        private static Assembly Domain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            // 自定义程序集解析逻辑
            return Assembly.LoadFrom("Path/To/MyAssembly.dll");
        }
    }
    
     

    • 这里使用 domain.AssemblyResolve 事件添加了自定义的程序集解析逻辑,当程序集无法找到时,会调用 Domain_AssemblyResolve 方法,你可以根据需要从自定义位置加载程序集。

 

2、异常处理和隔离

 

  1. 隔离可能引发异常的代码:
    • 将可能引发异常的代码放在单独的 AppDomain 中运行,避免影响主程序的执行。

    收起
    csharp
     
    using System;
    
    class Program
    {
        static void Main()
        {
            AppDomain domain = AppDomain.CreateDomain("ExceptionDomain");
            try
            {
                domain.DoCallBack(ExceptionThrowingMethod);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Caught exception from another AppDomain: {ex.Message}");
            }
            finally
            {
                AppDomain.Unload(domain);
            }
        }
    
        static void ExceptionThrowingMethod()
        {
            throw new Exception("This is an exception from another AppDomain");
        }
    }
    
     

    • 在这个例子中,ExceptionThrowingMethod 方法被放在新的 AppDomain 中执行,当它抛出异常时,不会直接导致主程序崩溃,而是可以在主程序中进行异常捕获。

 

3、监控资源使用

 

  1. 监控内存使用:
    • 可以在 AppDomain 中监控内存使用情况,帮助优化性能。

    收起
    csharp
     
    using System;
    using System.Reflection;
    
    class Program
    {
        static void Main()
        {
            AppDomain domain = AppDomain.CreateDomain("MemoryMonitorDomain");
            try
            {
                domain.MonitoringIsEnabled = true;
                // 执行一些操作
                object instance = domain.CreateInstanceFromAndUnwrap("MyAssembly.dll", "MyClass");
                // 可以调用一些方法等操作
                long memoryUsed = domain.MonitoringSurvivedMemorySize;
                Console.WriteLine($"Memory used in the AppDomain: {memoryUsed} bytes");
            }
            finally
            {
                AppDomain.Unload(domain);
            }
        }
    }
    
     

    • 通过将 domain.MonitoringIsEnabled 设置为 true,可以启用对该 AppDomain 的内存使用监控,使用 domain.MonitoringSurvivedMemorySize 可以获取该 AppDomain 存活对象占用的内存大小。

 

4、跨 AppDomain 通信

 

  1. 使用 MarshalByRefObject 实现跨 AppDomain 通信:
    收起
    csharp
     
    using System;
    
    public class RemoteObject : MarshalByRefObject
    {
        public string GetMessage()
        {
            return "Hello from another AppDomain";
        }
    }
    
    class Program
    {
        static void Main()
        {
            AppDomain domain = AppDomain.CreateDomain("CommunicationDomain");
            try
            {
                RemoteObject remoteObj = (RemoteObject)domain.CreateInstanceAndUnwrap(typeof(RemoteObject).Assembly.FullName, typeof(RemoteObject).FullName);
                string message = remoteObj.GetMessage();
                Console.WriteLine(message);
            }
            finally
            {
                AppDomain.Unload(domain);
            }
        }
    }
    
     

    • RemoteObject 继承自 MarshalByRefObject,这允许它被跨 AppDomain 调用。当在主 AppDomain 中调用 GetMessage 方法时,实际的调用会被代理到另一个 AppDomain 中执行,实现了跨 AppDomain 的通信。

 

5、配置不同的安全策略

 

    1. 设置不同的安全权限:
      收起
      csharp
       
      using System;
      using System.Security;
      using System.Security.Permissions;
      using System.Security.Policy;
      
      class Program
      {
          static void Main()
          {
              Evidence ev = new Evidence();
              ev.AddHostEvidence(new Zone(SecurityZone.Internet));
              PermissionSet internetPermissionSet = SecurityManager.GetStandardSandbox(ev);
              AppDomain domain = AppDomain.CreateDomain("SecureDomain", ev, new AppDomainSetup(), internetPermissionSet);
              try
              {
                  // 在安全受限的 AppDomain 中执行代码
              }
              finally
              {
                  AppDomain.Unload(domain);
              }
          }
      }
      
       

      • 这里使用 Evidence 和 PermissionSet 为新创建的 AppDomain 配置了安全权限,使其只能在特定的安全范围内运行,例如将其权限限制为从 Internet 区域下载的代码的权限。

 

posted @   卖雨伞的小男孩  阅读(36)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示