在析构函数中调用BeginInvoke

7

我有一个 WPF 应用程序的代码如下:

public class MyTextBox : System.Windows.Controls.TextBox, IDisposable
{
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        Dispatcher.BeginInvoke((Action) delegate
        {
            // do work on member variables on the UI thread.
        });
    }

    ~MyTextBox()
    {
        Dispose(false);
    }
}

dispose方法从未被显式调用,因此析构函数会调用它。在这种情况下,似乎对象将在BeginInvoke委托在UI线程上触发之前被销毁。尽管如此,它似乎仍然有效。这里发生了什么?这是安全的吗?


有关终结器的更多有趣内容,请查看当你所知道的一切都是错的,第二部分 - default
这是对象复活。我以前从未在实践中见过这种情况。很可能你的TextBox不应该一个终结器(你在试图做什么?)。 - usr
除了是一个坏主意,我没有看到它有任何好的用途... - AK_
我还没有收到原开发人员的回复,关于为什么要这样做。我的兴趣更多地在于当你这样做时实际上会发生什么。我想我的困惑在于将析构函数的调用和垃圾回收等同起来了。不过我需要更好地阅读相关资源。 - JonDrnek
Eric Lippert刚刚写了一篇关于finalizers的文章。这绝对值得一看 - Frank J
2个回答

4
在这种情况下,看起来对象将在委托的BeginInvoke在UI线程上触发之前被销毁。
终结器将工作排队到UI消息循环中。对象可能会在实际委托在UI线程上调用之前完成其终结器方法,但这并不重要,因为无论如何委托都会被排队。
这里发生了什么?
您正在从终结器向UI排队工作。
这是否安全?
安全是一个广义的术语。我会这样做吗?绝对不会。从终结器调用UI元素的操作看起来非常奇怪,特别是考虑到这是一个TextBox控件。我建议您充分了解运行终结器的保证和不保证。首先,运行终结器并不意味着对象立即在内存中清除。

我建议阅读 @EricLippert 的文章:为什么你所知道的一切都是错的第一部分第二部分


成员类已经被收集是不可能的。我们知道这一点,因为它正在从有效的托管引用中访问,所以GC不允许收集它。如果他正在访问他已经作为处理的一部分清理的任何未管理的资源,那么可能会导致问题。 - Servy
@Servy 由于某种原因,我认为一旦进入f-reachable队列,它就被视为死亡(不是根)。我删除了无关部分。 - Yuval Itzchakov
实际上恰恰相反。它在可回收队列中的事实意味着它非常活跃。但是它将失去自动注册以进行终结,因此如果发送到 BeginInvoke 的方法最终复活该对象,则可能需要重新注册以进行终结。主要问题是一旦进入终结状态,您就失去了所有关于何时发生这种情况的意识。如果应用程序正在关闭,为什么要为调度程序排队更多的工作?这绝对不是完成任何工作的正确方式! - Lasse V. Karlsen
@Lasse 当然。我回忆了一下f-reachable队列,它确实是有效的。 - Yuval Itzchakov

3
当您调用 BeginInvoke 时,您正在向调度程序的队列中添加一个委托,并且该委托将指向具有对已调用 Dispose 的对象的引用的对象。由于可以通过根变量访问对象的引用,因此该对象不符合收集条件。
现在,对于使用此代码的其他人来说,这将非常令人困惑,因此如果可能的话,应尽量避免“复活”已经完成终结的对象。

这取决于他在函数内部做什么...它可能不会使对象复活... - AK_
@AK_ 至少在操作执行完之前,它会使对象复活;由于该方法试图将已销毁的对象视为有效对象进行处理,所以我认为这是一种复活形式。当然,这本质上是一种非技术性的短语,因此不够精确。正如之前所说,精确的表述是只要该委托持有(间接)引用对象,该对象就无法被回收。 - Servy
委托为什么要持有对象的引用? - AK_
@AK_ 委托将持有对编译器生成的类的引用,该类用于将匿名方法创建为命名方法。该编译器生成的类将具有一个字段,其值将设置为当前正在处理的对象,因为该对象实例被匿名方法关闭(因为它在匿名方法中访问this的字段)。因此,委托具有此匿名方法的实例(以及指向表示匿名方法的方法的指针),并且该对象实例具有对正在处理的对象的引用。 - Servy
如果您引用封闭类或其字段,才会发生这种情况。我的意思是,例如,如果他只是在匿名方法中打印一些内容,我认为它不应该捕获封闭对象。虽然我老实说记不清了... - AK_
@AK_ 他在问题中明确表示匿名方法将访问对象的字段。请查看他在方法体中的注释。 - Servy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接