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

时间:2011-03-28 18:40:40

标签: c# .net exception serialization appdomain

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

因为版本2.0中.NET的默认行为是在任何AppDomain有未处理的异常时拆除整个过程,所以我们通过在App.config中使用“legacyUnhandledExceptionPolicy”选项然后手动拆除该过程来完成此操作如果未处理的异常位于主AppDomain中,或者如果它位于加载项中则卸载相应的AppDomain。

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

相反,我们得到一个SerializationException,在主AppDomain中显示为UnhandledException,导致我们的应用程序自行崩溃。

我可以想到这个问题的一些可能的解决方案:

  • 我们无法拆除未处理的SerializationExceptions(yuck)的过程。

  • 我们可以阻止异常从子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);
}

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

1 个答案:

答案 0 :(得分:2)

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

以下代码应该提供一些想法。

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