0

我正在学习C#。我阅读了 Andrew Troelsen “C# and the .NET Platform”和 Jeffrey Richter 的“CLR via C#”的书籍。现在,我正在尝试制作应用程序,它将从某个目录加载程序集,将它们推送到 AppDomain 并运行包含的方法(支持插件的应用程序)。这是公共接口所在的DLL。我将它添加到我的应用程序以及所有带有插件的 DLL 中。主库.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();在另一个域中运行(也许我做错了?)未处理的异常将删除它运行的域,但默认域将起作用。但在我的情况下 - 默认域在另一个域中发生异常后崩溃。请告诉我我做错了什么?

4

2 回答 2

4

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

因此,我们的应用程序将删除引发异常的 AppDomain,通知用户并继续。或者,如果异常来自主 AppDomain,它将简单地 FailFast。

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

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

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

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

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

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

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

于 2013-05-03T13:32:36.987 回答
1

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

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

于 2013-05-03T10:25:54.797 回答