当静态构造函数/类型初始化程序失败时,来自GC线程的未处理异常

时间:2011-01-25 08:52:50

标签: c# garbage-collection

(标题是:“TypeLoadException并不总是包含在使用了Reflection的TargetInvocationException中”)

使用BLToolkit我发现了一个有趣的事实 - methodInfo.Invoke并不总是在调用方法中捕获异常。

参见示例 - 它在方法的静态构造函数中模拟异常,通过反射调用。

问题是TestComponent继承自Component AND并重写了Dispose方法。 所以在这个示例中将有2条消息 - 一个“句柄”和一个“unhandle” - 看起来组件在较低级别的Reflection内部有不同的处理。

如果我们注释掉方法 Dispose(bool disposing) - 我们只会收到“句柄”消息。

任何人都可以解释为什么会发生并提出解决方案吗? BLToolkit中的try-catch无法标记为答案 - 我不是他们团队的成员:)

    class Program
{
    static void Main()
    {
        AppDomain.CurrentDomain.UnhandledException +=
            (sender, eventArgs) => Console.WriteLine("unHandled " + eventArgs.ExceptionObject.GetType().FullName);
        try
        {
            try
            {
                var instance = Activator.CreateInstance(typeof(ComponentExecutor));
                MethodInfo mi = typeof(ComponentExecutor).GetMethod("Do");
                BindingFlags bf = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly |
                                  BindingFlags.InvokeMethod;

                mi.Invoke(instance, bf, null, new object[0], CultureInfo.InvariantCulture);
            }
            catch (TargetInvocationException tarEx)
            {
                throw tarEx.InnerException;
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("Handled " + ex.GetType().FullName);
        }
    }

    class ComponentExecutor
    {
        public void Do()
        {
            new TestComponent().Do();
        }
    }
    class TestComponent : Component
    {
        static TestComponent()
        {
            throw new NullReferenceException();
        }
        [MethodImpl(MethodImplOptions.NoInlining)]
        public void Do()
        {
            Console.WriteLine("Doing");
        }
        protected override void Dispose(bool disposing)
        {
            Console.WriteLine("Disposing");
            base.Dispose(disposing);
        }
    }
}

1 个答案:

答案 0 :(得分:8)

这与反思无关;你只得到了一个非常相似的结果:

    AppDomain.CurrentDomain.UnhandledException +=
        (sender, eventArgs) => Console.WriteLine("unHandled " + eventArgs.ExceptionObject.GetType().FullName);
    try
    {
        new TestComponent();
    }
    catch (Exception ex)
    {
        Console.WriteLine("Handled " + ex.GetType().FullName);
    }
    Console.WriteLine("happy");

它实际上写的是“快乐”;未处理的异常似乎来自GC,可能是因为它试图收集一个部分构造的对象(它从不调用实例构造函数),然后它就会崩溃...特别注意它们在不同的线程上(我很确定未处理的是在GC线程上)。作为GC /线程,它可能是一种痛苦的重现;我在本地添加了GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);,只是为了强制GC发生,所以我经常看到它。迷人。

由于实例构造函数(TestComponent())和终结器(~TestComponent())都没有被调用,你有没有办法(我可以告诉)解决这个问题。< / p>

遗憾的是,我可以在这里建议的主要问题是:没有类型初始化器失败:(

可以工作的一件事就是欺骗运行时:

object obj = FormatterServices.GetUninitializedObject(typeof(TestComponent));

这仍然会遇到类型初始化程序并失败,但该对象似乎并没有丢失。也许这条路线没有标记为最终确定。因此GC不会那么讨厌它。

如果类型初始值设定项被禁止,您仍然会遇到使用此对象的主要问题。

因此,在您的代码中,您可能有:

class ComponentExecutor
{
    public void Do()
    {
        using (var tc = (TestComponent)
                FormatterServices.GetUninitializedObject(typeof(TestComponent)))
        {
            // call the ctor manually
            typeof(TestComponent).GetConstructor(Type.EmptyTypes).Invoke(tc, null);
            // maybe we can skip this since we are `using`
            GC.ReRegisterForFinalize(tc); 
            tc.Do();
        }
    }
}

现在:

  • 当类型初始化程序失败时,没有最终确定(它不需要 - 对象此时可能无法执行任何操作,因为它从未运行构造函数)
  • 当类型初始化程序工作时,执行ctor并将其注册到GC - 所有hunky dory