C# 另一个域引发异常时应用程序崩溃

C# 另一个域引发异常时应用程序崩溃,c#,exception,.net-assembly,appdomain,C#,Exception,.net Assembly,Appdomain,我在学C。我读过安德鲁·特罗尔森的《C与.NET平台》和杰弗里·里克特的《通过C的CLR》。现在,我正在尝试创建一个应用程序,它将从某个目录加载程序集,将它们推送到AppDomain,并运行包含的方法(支持插件的应用程序)。这是DLL,其中是公共接口。我将它添加到我的应用程序中,并添加到所有带有插件的DLL中。 MainLib.DLL namespace MainLib { public interface ICommonInterface { void ShowDllName(); }

我在学C。我读过安德鲁·特罗尔森的《C与.NET平台》和杰弗里·里克特的《通过C的CLR》。现在,我正在尝试创建一个应用程序,它将从某个目录加载程序集,将它们推送到AppDomain,并运行包含的方法(支持插件的应用程序)。这是DLL,其中是公共接口。我将它添加到我的应用程序中,并添加到所有带有插件的DLL中。 MainLib.DLL

namespace MainLib
{
public interface ICommonInterface
{
    void ShowDllName();
}
}
以下是插件: 插件无异常

namespace PluginWithOutException
{
public class WithOutException : MarshalByRefObject, ICommonInterface
{
    public void ShowDllName()
    {
        MessageBox.Show("PluginWithOutException");
    }

    public WithOutException()
    {

    }
}
}
还有一个: 插件异常

namespace PluginWithException
{
public class WithException : MarshalByRefObject, ICommonInterface
{
    public void ShowDllName()
    {
        MessageBox.Show("WithException");
        throw new NotImplementedException();
    }
}
}
这是一个应用程序,它加载DLL并在另一个AppDomain的应用程序中运行它们

namespace Plug_inApp
{   
class Program
{

    static void Main(string[] args)
    {

        ThreadPool.QueueUserWorkItem(CreateDomainAndLoadAssebly, @"E:\Plugins\PluginWithException.dll");

        Console.ReadKey();
    }
    public static void CreateDomainAndLoadAssebly(object name)
    {
        string assemblyName = (string)name;
        Assembly assemblyToLoad = null;
        AppDomain domain = AppDomain.CreateDomain(string.Format("{0} Domain", assemblyName));
        domain.FirstChanceException += domain_FirstChanceException;

        try
        {
            assemblyToLoad = Assembly.LoadFrom(assemblyName);
        }
        catch (FileNotFoundException)
        {
            MessageBox.Show("Can't find assembly!");
            throw;
        }

        var theClassTypes = from t in assemblyToLoad.GetTypes()
                            where t.IsClass &&
                                  (t.GetInterface("ICommonInterface") != null)
                            select t;
        foreach (Type type in theClassTypes)
        {
            ICommonInterface instance = (ICommonInterface)domain.CreateInstanceFromAndUnwrap(assemblyName, type.FullName);
            instance.ShowDllName();
        }

    }

    static void domain_FirstChanceException(object sender, System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs e)
    {
        MessageBox.Show(e.Exception.Message);
    }
}
}

我预计,如果我运行
instance.ShowDllName()
在另一个域中(可能是我做错了?)未处理的异常将删除它运行的域,但默认域将工作。但在我的例子中,默认域在另一个域发生异常后崩溃。请告诉我我做错了什么?

来自子
AppDomain
的未处理异常将导致子
AppDomain
关闭,然后它将被抛出主
AppDomain
。如果不在那里处理,主
AppDomain
也会下降。
FirstChanceException
不处理未处理的异常。检查事件的文档。它是为应用程序抛出的所有执行而引发的,即使是您正在处理的执行。它使您有机会检查抛出(已处理或未处理)的所有异常


对加载项的所有调用都应在try/catch块中。捕获那里的所有异常并记录它们。您甚至可以将插件标记为不可靠(因为它不稳定),并在下次应用程序启动时默认不加载它。或者让用户决定做什么。MS Office应用程序用于禁用不稳定的插件(导致应用程序崩溃的插件),然后用户必须从“关于”对话框中再次启用它们(我开发MS Office加载项已经有一段时间了,我不知道它们在Office 2010及更高版本中是否遵循相同的方法)。查看
System.AddIn
团队提供的此示例,了解如何使用。它还提到,无论您做什么,来自child
AppDomain
的子线程的未处理异常都将导致整个进程停止。

来自child
AppDomain
的未处理异常将导致child
AppDomain
停止,然后它将被抛出主
AppDomain
。如果不在那里处理,主
AppDomain
也会下降。
FirstChanceException
不处理未处理的异常。检查事件的文档。它是为应用程序抛出的所有执行而引发的,即使是您正在处理的执行。它使您有机会检查抛出(已处理或未处理)的所有异常


对加载项的所有调用都应在try/catch块中。捕获那里的所有异常并记录它们。您甚至可以将插件标记为不可靠(因为它不稳定),并在下次应用程序启动时默认不加载它。或者让用户决定做什么。MS Office应用程序用于禁用不稳定的插件(导致应用程序崩溃的插件),然后用户必须从“关于”对话框中再次启用它们(我开发MS Office加载项已经有一段时间了,我不知道它们在Office 2010及更高版本中是否遵循相同的方法)。查看
System.AddIn
团队提供的此示例,了解如何使用。它还提到,来自子
AppDomain
的子线程的未处理异常将导致整个进程停止,无论您做什么。

如果您确实需要,有一种方法可以对此进行控制。我们这样做是因为我们的外接程序可以在我们的团队之外编写,并且我们尽可能避免我们的应用程序因为其他人的外接程序而崩溃

因此,我们的应用程序将取下引发异常的AppDomain,通知用户,然后继续。或者,如果异常来自主AppDomain,它将快速失效

在App.config中,您需要以下内容:

<configuration>
 <runtime>
  <legacyUnhandledExceptionPolicy enabled="true" />
 </runtime>
</configuration>

这将恢复到未处理异常的遗留行为,并允许您自行决定是终止整个进程还是仅终止AppDomain

您仍然需要处理一些其他问题,例如确定哪些异常来自哪个AppDomain

另一个问题是,并非所有异常都是可序列化的,这意味着当它们跨越AppDomain边界时,某些异常将变成序列化异常

因为我们的外接程序实现了一个公共基类,所以我们通过将未处理的异常处理程序放入外接程序本身来解决这些问题。然后,我们连接到
AppDomain.CurrentDomain.UnhandledException
TaskScheduler.UnobservedTaskException
并调用
AppDomain.Unload(AppDomain.CurrentDomain)
来终止外接程序


这并不完美,但对我们的项目来说效果很好。

如果你真的需要,有一种方法可以控制它。我们这样做是因为我们的外接程序可以在我们的团队之外编写,并且我们尽可能避免我们的应用程序因为其他人的外接程序而崩溃

因此,我们的应用程序将取下引发异常的AppDomain,通知用户,然后继续。或者,如果异常来自主AppDomain,它将快速失效

在App.config中,您需要以下内容:

<configuration>
 <runtime>
  <legacyUnhandledExceptionPolicy enabled="true" />
 </runtime>
</configuration>

这将恢复到未处理异常的遗留行为,并允许您自行决定是终止整个进程还是仅终止AppDomain

您仍然需要处理一些其他问题,例如确定哪些异常来自哪个AppDomain

另一个问题是,并非所有异常都是可序列化的,这意味着有些异常在发生时会变成序列化异常