【问题标题】:Unhandled exception coming from GC thread when a static-constructor / type-initializer fails静态构造函数/类型初始化程序失败时来自 GC 线程的未处理异常
【发布时间】:2011-01-25 08:52:50
【问题描述】:

(标题是:“TypeLoadException 并不总是包装到使用反射的 TargetInvocationException”)

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

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

问题是 TestComponent 继承自 Component 并且重写了 Dispose 方法。 因此,在此示例中将有 2 条消息 - 一条“处理”和一条“未处理” - 似乎组件在较低级别的反射内部具有不同的处理方式。

如果我们注释掉方法 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);
        }
    }
}

【问题讨论】:

标签: c# garbage-collection


【解决方案1】:

这与反射无关;你会得到一个非常相似的结果:

    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()) 都没有被调用,因此您不可能(我可以说)解决这个问题。

遗憾的是,我在这里建议的主要内容是:不要让类型初始化程序失败:(

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

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 - all hunky dory

【讨论】:

  • 感谢您的解决方案。但是与 FormatterServices.GetUninitializedObject 有什么区别?这个对象后面会用到(像Do方法的返回值一样),所以使用不适用。
  • @Lonli - 以前,类型初始化程序(静态.cctor)失败我们有一个部分构造的对象标记为完成 .这需要收集,但终结器会失败(相同的.cctor)。而且由于终结器在 GC 线程上运行 - 非常坏消息。通过使用GetUninitializedObject,我们得到了一个not 标记为最终确定的对象,因此即使.cctor 失败,我们也可以。然后我们可以手动修复它。我完全认为这是丑陋和骇人听闻的,但它似乎有效。
  • 是的,丑陋的:) 但工作。我已经向 BLToolkit 页面添加了问题,但至少现在它应该可以工作了。顺便说一句,我不确定这是否是 GC 的错误 - 如果类型初始化程序失败,则调用对象的终结器。
  • @Lonli - 我同意;我已经登录连接:connect.microsoft.com/VisualStudio/feedback/details/638078/…
猜你喜欢
  • 2010-11-22
  • 2015-09-11
  • 1970-01-01
  • 1970-01-01
  • 2011-02-09
  • 1970-01-01
  • 2018-09-23
  • 1970-01-01
  • 2010-10-16
相关资源
最近更新 更多