当另一个域抛出异常时,应用程序崩溃

本文关键字:应用程序 崩溃 抛出异常 另一个 | 更新日期: 2023-09-27 18:11:59

我正在学习c#。我读了Andrew Troelsen的《c#和。net平台》和Jeffrey Richter的《通过c#实现CLR》。现在,我正在尝试制作一个应用程序,它将从某些目录加载程序集,将它们推送到AppDomain并运行包含的方法(支持插件的应用程序)。这里是DLL里面的公共接口。我将它添加到我的应用程序中,并添加到所有带有插件的dll中。MainLib.DLL

namespace MainLib
{
public interface ICommonInterface
{
    void ShowDllName();
}
}

以下是插件:PluginWithOutException

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

和另一个:PluginWithException

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,它将简单地FailFast。

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

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

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

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

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

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

这不是完美的,但它在我们的项目中工作得很好。

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

所有对外接程序的调用都应该在try/catch块中。捕获那里的所有异常并记录它们。你甚至可以将插件标记为不可靠(因为它不稳定),并且在下次启动应用程序时默认不加载它。或者让用户决定怎么做。MS Office应用程序曾经禁用不稳定的插件(那些使应用程序崩溃的插件),然后用户必须从"关于"对话框中重新启用它们(我开发MS Office插件已经有一段时间了,我不知道它们在Office 2010和以后的版本中是否遵循相同的方法)。看看System.AddIn团队制作的这个例子,了解如何检测外接程序故障。它还提到,无论您做什么,子AppDomain的子线程未处理的异常都会使整个进程崩溃。