终结器和Dispose方法

4
我有一个名为BackgroundWorker的类,它有一个持续运行的线程。要关闭这个线程,需要将一个名为stop的实例变量设置为true
为了确保在使用完类后释放该线程,我添加了IDisposable和一个调用Dispose()的终结器。假设stop = true确实会导致该线程退出,那么这段代码是正确的吗?从终结器中调用Dispose是可以的,对吗?
如果object继承了IDisposable,那么终结器应该始终调用Dispose,对吗?
/// <summary>
/// Force the background thread to exit.
/// </summary>
public void Dispose()
{
    lock (this.locker)
    {
        this.stop = true;
    }
}

~BackgroundWorker()
{
    this.Dispose();
}

第一段中有错别字吗?“false” 应该改为 “true”,对吗? - Blorgbeard
6个回答

10

首先,严重警告。不要像你现在这样使用终结器。如果在终结器中使用锁定操作,会导致一些非常不好的后果。简而言之,不要这样做。现在回到原来的问题。

public void Dispose()
{
    Dispose(true);
    GC.SuppressFinalize(this);
}

/// <summary>
/// Force the background thread to exit.
/// </summary>
protected virtual void Dispose(bool disposing)
{
    if (disposing)
    {
        lock (this.locker)
        {
            this.stop = true;
        }
    }
}

~BackgroundWorker()
{
    Dispose(false);
}

设置 finalizer 的唯一原因是允许子类扩展和释放 非托管资源。如果没有子类,则应封闭类并完全删除 finalizer。


3
关于“严重警告” - 他在终结器中没有使用锁定,只在Dispose()中使用了锁定,这是由于“if(disposing)”检查的原因。话虽如此,一个易失性布尔变量可能更好。这段代码仅在调用Dispose()时停止工作线程,这是合理和有效的用法。 - Marc Gravell

4

问一下,为什么不能使用完全支持取消的常规BackgroundWorker

关于锁 - 一个易失的布尔字段可能会更少麻烦。

然而,在这种情况下,您的终结器没有做任何有趣的事情,特别是考虑到“if(disposing)” - 即它只在Dispose()期间运行有趣的代码。个人建议只使用IDisposable,不提供终结器:应该使用Dispose()清理。


3

你的代码没问题,尽管在finalizer中加锁有点“可怕”,我建议避免这样做-如果发生死锁...我不确定会发生什么,但肯定不是好事。不过,如果你确保安全,这应该不是个问题。大部分情况下都是。垃圾回收的内部机制很痛苦,希望你永远不必看到它们 ;)

正如Marc Gravell所指出的,一个volatile bool可以让你摆脱锁,从而缓解这个问题。如果可以,实现这个更改。

nedruod的代码将赋值放在if (disposing)检查内部,这是完全错误的-线程是一种非托管资源,即使没有明确的处理也必须停止。你的代码没问题,我只是指出你不应该采纳那段代码片段中给出的建议。

是的,如果实现IDisposable模式,几乎总是应该从finalizer中调用Dispose()。完整的IDisposable模式比你拥有的要复杂一些,但并不总是需要它-它仅提供了两个额外的可能性:

  1. 检测是否调用了Dispose()或者正在执行finalizer(在对象被finalize之外,你不能触碰任何托管资源);
  2. 使子类能够重写Dispose()方法。

3
的确,在 finalizer 中使用锁是令人担忧的,因此也不被鼓励!因为只有一个线程来运行所有的 finalizer,如果一个 finalizer 被阻塞,其他的 finalizer 就无法运行。因此,在 finalizer 中不建议使用任何锁。 - Josep

0
实现终结器的对象需要引用一个标志——存储在另一个对象中——线程将能够看到;线程必须没有任何对实现终结器的对象的强引用,直接或间接。终结器应该使用类似于CompareExchange的东西来设置标志,线程应该使用类似的方式来测试它。请注意,如果一个对象的终结器访问另一个对象,则另一个对象可能已经被终结,但它仍然存在。如果终结器以不会受到其终结的干扰的方式引用其他对象,那么引用其他对象是可以的。如果你所做的只是设置一个标志,那么没问题。

0

"stop"实例变量是属性吗?如果不是,那么在终结器中设置它没有特别的意义 - 没有任何东西再引用该对象,因此也没有任何东西可以查询该成员。

如果您实际上正在释放资源,则使Dispose()和终结器执行相同的工作(首先测试是否仍需要执行该工作)是一个很好的模式。


"stop" 是一个私有变量。如果你不能从终结器中调用 this.Dispose(),那么我该如何确保对象在被 GC 清理时被处理? - core
我并不是说你不能调用Dispose方法 - 我的意思是,如果它只是一个变量,那也没什么关系 - 对象总会消失的。 - Michael Burr

0
你需要完整的可释放模式,但停止必须是线程可以访问的内容。如果它是被处理的类的成员变量,那就不好了,因为它不能引用一个已释放的类。考虑拥有一个线程拥有并在处理时发出信号的事件。

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