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

时间:2013-05-02 21:26:15

标签: c# exception .net-assembly appdomain

我正在研究C#。我读过Andrew Troelsen的书“C#和.NET平台”以及Jeffrey Richter的“CLR via C#”。现在,我正在尝试创建应用程序,它将从某个目录加载程序集,将它们推送到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();(也许我做错了?),未处理的异常将删除运行它的域,但默认域将起作用。但就我而言 - 在另一个域发生异常后,默认域崩溃。请告诉我我做错了什么?

2 个答案:

答案 0 :(得分:3)

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

因此,我们的应用程序将删除抛出异常的AppDomain,通知用户,然后继续。或者,如果异常来自主AppDomain,它将只是FailFast。

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

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

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

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

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

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

这并不完美,但它对我们的项目非常有效。

答案 1 :(得分:1)

来自孩子AppDomain的未处理异常将导致孩子AppDomain失效,然后它将被抛入您的主AppDomain。如果你不在那里处理它,主AppDomain也会下降。 FirstChanceException不处理未处理的异常。查看FirstChanceException事件的文档。它适用于您的应用程序抛出的所有exec,即使对于您正在处理的应用程序也是如此。它使您有机会检查抛出的所有异常(处理或未处理)。

所有对加载项的调用都应该在try / catch块中。捕获所有异常并记录它们。您甚至可以将插件标记为不可靠(因为它不稳定),并且在下次启动应用程序时默认不加载它。或者让用户决定做什么。 MS Office应用程序用于禁用不稳定的插件(崩溃应用程序的插件),然后用户必须从about对话框再次启用它们(自从我开发MS Office插件以来我已经有一段时间了,我不知道如果他们在Office 2010及更高版本中遵循相同的方法)。请System.AddIn小组查看有关如何detect add-in failures的示例。它还提到,无论你做什么,来自孩子AppDomain的子线程的未处理异常都会使整个过程失效。