处理来自子 AppDomain 的不可序列化的未处理异常

发布于 2024-10-27 14:01:24 字数 2672 浏览 3 评论 0原文

我们使用 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();
    }
}



编辑:我使用 Reflector 来查看是否有任何方法可以访问跨 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);
}

因此它在方法中本地创建格式化程序,并且显然无法附加我自己的代理项......我认为这使得在这个方向上的任何更多努力都是徒劳的。

We are using System.AddIn to load add-ins into separate child AppDomains, and we unload the add-in AppDomain if it has an unhandled exception.

Because the default behaviour of .NET from version 2.0 was to tear down the entire process if any AppDomain has an unhandled exception, we do this by using the "legacyUnhandledExceptionPolicy" option in our App.config and then manually tearing down the process if the unhandled exception was in the main AppDomain, or unloading the appropriate AppDomain if it was in an add-in.

This is all working great, except for one small issue: Unhandled exceptions always bubble up from the child AppDomains to the main one, and if they aren't serializable they can't successfully cross the AppDomain boundary.

Instead we get a SerializationException appearing as an UnhandledException in the main AppDomain, causing our application to tear itself down.

I can think of a few possible solutions to this problem:

  • We could not tear down the process for unhandled SerializationExceptions (yuck).

  • We could stop exceptions from propagating from the child AppDomain to the main AppDomain.

  • We could replace the unserializable exceptions with serializable ones, maybe using serialization surrogates and serialization binders. [Edit: see end for why this isn't possible using surrogates]

However the first one is pretty horrible, and I've been unsuccessful in figuring out how to do either of the other options with cross-AppDomain remoting.

Can anyone offer some advice? Any help is appreciated.

To reproduce create a console application with the following App.config:

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

And the following code:

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();
    }
}


EDIT: I used reflector to see if there was any way to gain access to the BinaryFormatter used in cross-AppDomain remoting, but it ends up in this code inside the CrossAppDomainSerializer class:

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);
}

So it creates the formatter locally in the method, and there is clearly no way to attach my own surrogate... I think this makes any more efforts in that direction futile.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

对不⑦ 2024-11-03 14:01:24

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

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

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);
    }
}

I would handle the exceptions in the remote application domain. First I would create a new assembly with the exception handling code and then I would load it into child application domain.

The following code should give some ideas.

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);
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文