【问题标题】:Dealing with non-serializable unhandled exceptions from a child AppDomain处理来自子 AppDomain 的不可序列化的未处理异常
【发布时间】:2011-07-24 17:26:25
【问题描述】:

我们正在使用 System.AddIn 将加载项加载到单独的子 AppDomain 中,如果加载项 AppDomain 有未处理的异常,我们将卸载它。

因为 .NET 从 2.0 版开始的默认行为是在任何 AppDomain 有未处理的异常时终止整个进程,因此我们通过在 App.config 中使用“legacyUnhandledExceptionPolicy”选项然后手动终止进程来做到这一点如果未处理的异常在主 AppDomain 中,或者在加载项中卸载相应的 AppDomain。

这一切都很好,除了一个小问题:未处理的异常总是从子 AppDomain 冒泡到主 AppDomain,如果它们不可序列化,它们就无法成功跨越 AppDomain 边界。

相反,我们在主 AppDomain 中以 UnhandledException 的形式出现 SerializationException,导致我们的应用程序自行关闭。

我能想到几个可能的解决方案:

  • 我们无法为未处理的 SerializationExceptions 取消进程(糟糕)。

  • 我们可以阻止异常从子 AppDomain 传播到主 AppDomain。

  • 我们可以用可序列化的异常替换不可序列化的异常,可能使用序列化代理和序列化绑定。 [编辑:请参阅结尾,了解为什么使用代理无法实现这一点]

但是第一个非常可怕,我一直未能弄清楚如何使用跨 AppDomain 远程处理执行其他任何一个选项。

谁能给点建议?任何帮助表示赞赏。

使用以下 App.config 重现创建控制台应用程序:

<?xml version="1.0"?>
<configuration>
  <runtime>
    <legacyUnhandledExceptionPolicy enabled="true"/>
  </runtime>
</configuration>

还有如下代码:

class Program
{
    private class NonSerializableException : Exception
    {
    }

    static void Main(string[] args)
    {
        AppDomain.CurrentDomain.UnhandledException += MainDomain_UnhandledException;

        AppDomain childAppDomain = AppDomain.CreateDomain("Child");
        childAppDomain.UnhandledException += ChildAppDomain_UnhandledException;

        childAppDomain.DoCallBack(
            () => new Thread(delegate() { throw new NonSerializableException(); }).Start());

        Console.ReadLine();
    }

    static void MainDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        Console.WriteLine("Main AppDomain Unhandled Exception: " + e.ExceptionObject);
        Console.WriteLine();
    }

    static void ChildAppDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        Console.WriteLine("Child AppDomain Unhandled Exception: " + e.ExceptionObject);
        Console.WriteLine();
    }
}



编辑:我使用反射器查看是否有任何方法可以访问跨 AppDomain 远程处理中使用的 BinaryFormatter,但它最终出现在 CrossAppDomainSerializer 类中的以下代码中:

internal static void SerializeObject(object obj, MemoryStream stm)
{
    BinaryFormatter formatter = new BinaryFormatter();
    RemotingSurrogateSelector selector = new RemotingSurrogateSelector();
    formatter.SurrogateSelector = selector;
    formatter.Context = new StreamingContext(StreamingContextStates.CrossAppDomain);
    formatter.Serialize(stm, obj, null, false);
}

所以它在方法中本地创建格式化程序,显然没有办法附加我自己的代理......我认为这使得在这个方向上的任何努力都是徒劳的。

【问题讨论】:

  • 嘿,詹姆斯,你找到解决办法了吗?
  • @Joshjje,不是一个好的解决方案......我们最终只处理了每个 AppDomain 中未处理的异常,然后让 AppDomain 从那里卸载自己。这显然需要信任每个加载项来设置这个系统,谢天谢地,我们可以为我们的应用程序。不过,我没有找到从主 AppDomain 中管理整个过程的方法。

标签: c# .net exception serialization appdomain


【解决方案1】:

我将处理远程应用程序域中的异常。首先,我将使用异常处理代码创建一个新程序集,然后将其加载到子应用程序域中。

下面的代码应该会给出一些想法。

class Program {
    private class NonSerializableException : Exception { }

    static void Main(string[] args) {
        var childAppDomain = AppDomain.CreateDomain("Child");
        Console.WriteLine("Created child AppDomain #{0}.", childAppDomain.Id);

        // I did not create a new assembly for the helper class because I am lazy :)
        var helperAssemblyLocation = typeof(AppDomainHelper).Assembly.Location;
        var helper = (AppDomainHelper)childAppDomain.CreateInstanceFromAndUnwrap(
                helperAssemblyLocation, typeof(AppDomainHelper).FullName);
        helper.Initialize(UnloadHelper.Instance);

        childAppDomain.DoCallBack(
            () => new Thread(delegate() { throw new NonSerializableException(); }).Start());

        Console.ReadLine();
    }

    private sealed class UnloadHelper : MarshalByRefObject, IAppDomainUnloader {
        public static readonly UnloadHelper Instance = new UnloadHelper();

        private UnloadHelper() { }

        public override object InitializeLifetimeService() {
            return null;
        }

        public void RequestUnload(int id) {
            // Add application domain identified by id into unload queue.
            Console.WriteLine("AppDomain #{0} requests unload.", id);
        }
    }
}

// These two types could be in another helper assembly

public interface IAppDomainUnloader {
    void RequestUnload(int id);
}

public sealed class AppDomainHelper : MarshalByRefObject {
    public void Initialize(IAppDomainUnloader unloader) {
        AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
                unloader.RequestUnload(AppDomain.CurrentDomain.Id);
    }
}

【讨论】:

  • 感谢 mgronber,但即使使用此解决方案,NonSerializableException 仍会作为未处理的 SerializationException 传播到父 AppDomain。我无法将 SerializationException 识别为来自子 AppDomain,这迫使我要么忽略它,要么将其视为任何其他未处理的异常并终止应用程序。
  • 补充一点,我们实际上使用了与您提出的处理 Dispatcher.UnhandledException 事件类似的技术......它在那里工作得很好,因为您可以将异常标记为已处理并且它不会t 进一步传播。 AppDomain.UnhandledException 遗憾地不支持这一点,并且总是将异常冒泡到主 AppDomain :(
  • 如何在子域中执行代码?您能否使用辅助方法来调用代码并捕获任何未处理的异常?
  • 虽然我们不控制加载项的功能,但我们控制子应用程序域端的引导过程(我们为加载项开发人员提供构建框架)。 .. 因此,作为引导过程的一部分,我们可以从子 AppDomain 中挂钩 Dispatcher.UnhandledException 和 AppDomain.UnhandledException 事件,并在子 AppDomain 端处理它们。但是,即使我自己在子域中处理它们,我也无法阻止来自 AppDomain.UnhandledException 的异常冒泡到父 AppDomain。
  • @James Thurley:我的意思是当您调用加载项的入口点方法时,您可以在子域中捕获异常。但是,我刚刚意识到,如果加载项在其他线程中引发异常,这可能不起作用。如果您想真正确定加载项不会破坏您的进程,您应该有另一个进程来运行加载项。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-08-15
  • 1970-01-01
  • 2011-12-10
  • 2012-10-05
  • 1970-01-01
  • 1970-01-01
  • 2017-04-22
相关资源
最近更新 更多