析构函数没用? [英] Destructors are useless?

查看:89
本文介绍了析构函数没用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经在使用.NET移植到Mono上的C#代码

的上下文中遇到了析构函数的问题。会发生的情况是,在双CPU机器上,各种代码的b / b b b部分随机崩溃(很少)。在某些线程调用System.Environment.Exit()之后,这总是发生在

进程关闭期间。显然,

某种竞争条件。


注意以下内容仅适用于在进程中调用的析构函数

关闭 - 当进程正常执行时,GC调用的析构函数没有问题。


经过大量调试后,我沿着以下

行追踪到了一个析构函数:


~TomeClass

{

if(!_destroyed)

{

System.Console.Error.WriteLine(" Forgot to destroy SomeClass");

}

}


在Mono下,尝试写入控制台会导致进程崩溃,因为到时候

析构函数被调用垃圾收集器,I / O子系统已经部分地收集了垃圾,并且该过程在I / O子系统的内部死亡时出现NullPointerException

。 br />

.NET不是问题:在.NET中,c在所有事情之后,鞋底都不会被销毁

其他部分被销毁。 (有关详细信息,请参阅:
http://www.bluebytesoftware.com/blog...3-20c06ae539ae

有关Brian Grunkemeyer页面下三分之二的评论为了这个效果。)


除了我不能依赖这个,因为据我所知,规格并不能保证

控制台在进程关闭期间会挂起,因此它不是可移植的代码。


然后我开始阅读规范,发现破坏顺序无法保证

,如果A指的是B,那么B完全有可能在A之前完成。所以,这让我想到了什么是实际合法的b $ b如果析构函数可能在进程关闭期间被调用
,则在析构函数中执行。以下列出了*不合法的事情:


- 我无法取消引用任何内容。如果我这样做,那么参考对象的内存将保证

仍然存在。但是,该对象可能已经完成,因此,如果我在其上调用方法,可能不会长时间处于工作状态。


- 我不能在任何事情上调用静态方法。静态方法本身保证在那里。但是,静态方法的

实现可能依赖于已经完成的另一个静态对象。

见前一点。


- 我不能安全地在我自己的对象上调用虚方法。那是因为我自己的对象可能有派生的

部分,而派生的部分可能已经完成,虚拟方法最终可能会使用

从概念上不再存在的派生部分。


因此,据我所知,这并没有在析构函数中做很多事情。以下是我可以安全地做的事情:


我可以分配或读取我自己的任何数据成员,以及我的基类的任何可访问数据成员。

那是关于它的。


所以,我可以为所有具有引用类型的数据成员分配null,只是为了对GC好。但是那个

在大多数情况下都不是那么重要。


我可以阅读自己的数据成员。为了什么目的?好吧,断言我的程序状态仍然很好

形状,当然:


~TomeClass()

{

System.Diagnostics.Assert(_myMember!= null);

System.Diagnostics.Assert(_myOtherMember == null);

}


哎呀。我不能安全地调用静态方法,因为无论静态方法使用的是什么,都可能已经确定了
。正如我无法安全地写入控制台一样,我也无法安全地断言。


当然,我无法真正控制调用析构函数的时间。特别是,当一些线程调用Exit()时,我没有控制析构函数运行的
。因此,这些限制适用于* all *

析构函数,而不仅仅适用于静态对象。


嗯...这会让析构函数完全失效。没有什么,甚至没有断言,我可以安全地做。

当然,这引出了一个问题:为什么在我不能对它们做任何事情的时候有破坏者呢?至于我能看到,b $ b看到,析构函数中唯一的法律陈述实际上是无操作。

神秘,


Michi。

解决方案

建议避免使用终结器(它们实际上称为

''终结器'',而不是''desctructors'')尽可能。如果您需要发布

非托管资源,请实施IDisposable并遵循Microsoft推荐的IDisposable

实现设计模式。如果你的班级没有使用任何东西要处理,请不要使用终结器。


不过,你可以把终结器用好了实施微软的'b $ b一次性对象设计模式。在模式中,终结器是释放实例拥有的托管资源的最后一次机会。


-

真诚,

Dmytro Lapshyn [Visual Developer - Visual C#MVP]

" Michi Henning" < MI *** @ zeroc.com>在消息中写道

新闻:在************** @ tk2msftngp13.phx.gbl ...

我去过在将.NET开发的C#代码移植到Mono的上下文中遇到了析构函数的问题。会发生的情况是,在双CPU机器上,各种代码部分随机崩溃(很少)。在某些线程调用System.Environment.Exit()之后,这总是发生在
进程关闭期间。
显然,某种竞争条件。

请注意以下内容仅适用于在进程关闭时调用的析构函数 - 当进程正常执行时,由GC调用的析构函数没有问题经过大量的调试后,我跟踪了
后面的析构函数:

〜SomeClass
{
if(!_destroyed)
{/> System.Console.Error.WriteLine(" Forgot to destroy SomeClass");
}
}
<在Mono下,尝试写入控制台会导致进程崩溃
因为,当垃圾收集器调用析构函数时,I / O子系统已部分
已经收集了垃圾,并且该过程在某个地方因某个地方出现了NullPointerException而死亡I / O子系统的问题。

.NET没有问题:在.NET中,控制台不会被破坏,直到
其他所有东西都被销毁。 (有关详细信息,请参阅:
http://www.bluebytesoftware.com/blog...3-20c06ae539ae
有关Brian关于页面的三分之二的评论
Grunkemeyer为了这个效果。)

除了我不能依赖这个,因为据我所知,规格
不保证
控制台在进程关闭期间会挂起来,所以它不是便携式的代码。

然后我开始阅读规范,发现销毁订单无法保证,
如果A指的是B,那么B完全有可能在A之前完成。所以,这让我想到从内部做什么是合法的析构函数,如果在进程关闭期间可能会调用析构函数。以下是*不合法的事情清单:

- 我无法取消引用任何内容。如果我这样做,
参考的对象的内存保证仍然存在。但是,该对象可能已经完成
因此,如果我在其上调用方法,可能不会长时间处于正常工作状态。

- 我不能打电话什么都是静态方法。静态方法本身保证在那里。但是,静态方法的实现可能依赖于已经完成的另一个静态对象。
见前一点。

- 我无法安全地调用我自己的对象上的虚方法。那是因为
我自己的对象可能有一个派生的部分,而派生的部分可能已经完成了,而且
虚拟方法最终可能会使用
从概念上不再存在的派生部分。

所以,我无法在析构函数中做很多事情,只要我能看到。以下是我可以安全地做的事情:

我可以分配或阅读我自己的任何数据成员,以及我基类的任何可访问的数据成员。
那个'关于它。

所以,我可以为所有具有引用类型的数据成员分配null,
只是为了对GC好。但是,在大多数情况下,
确实不是那么重要。

我可以阅读自己的数据成员。为了什么目的?好吧,断言我的
程序状态仍然很好,当然:

〜SomeClass()
{
System.Diagnostics。断言(_myMember!= null);
System.Diagnostics.Assert(_myOtherMember == null);
}
哎呀。我不能安全地调用静态方法,因为静态方法使用的
实现可能已经完成了。正如我无法安全地写入控制台,
我也不能安全地断言。

当然,我无法真正控制调用析构函数的时间。特别是,当一些线程调用Exit()时,我无法控制析构函数的运行情况。因此,
这些限制适用于* all *
析构函数,而不仅仅适用于静态对象。

嗯......这会让析构函数完全失效。什么都没有,甚至没有断言,我可以安全地做。
当然,这引出了一个问题:为什么在我不能与他们做任何事情的时候有破坏者呢?据我所知,析构函数中唯一的法律陈述实际上是无操作。

神秘,

Michi。




2005年4月27日星期三17:40:26 +1000,Michi Henning写道:

嗯......这使得析构函数完全没用。没有什么,甚至没有断言,我可以安全地做。
当然,这引出了一个问题:当我不能对他们做任何事情时,为什么会有破坏者呢?据我所知,析构函数中唯一的法律陈述实际上是无操作。




如果您正在编写所有托管代码,那么这个声明本质上就是它。

在纯托管环境中,不需要编写析构函数。

析构函数对于释放非托管资源非常有用。垃圾

收藏家对如何清理非托管的
资源没有具体的了解。封装非托管资源的类确实具有

的知识。因此,在您的析构函数中,您将承担责任

,以确保正确释放任何非托管资源。让GC

处理您的托管资源。


另请注意,向对象添加析构函数可能会导致

为负对绩效的影响。具有析构函数的对象需要至少
至少两个垃圾收集。当GC运行时,它会回收内存以获得没有析构函数的
无法访问的对象。当时,它不能收集具有析构函数的无法访问的对象。相反,它将它们放在一个标记为准备完成的对象列表中。然后

调用该列表中对象的finalize方法(析构函数),然后

将它们从列表中删除。下次GC运行时,它们可以收集这些对象,因为它们现在不再位于准备好最终确定的对象列表中。因此,你已经添加了一个析构函数来对付你的对象,这只会增加它比其他对象更长时间挂起的可能性。

-

Tom Porterfield


Tom Porterfield写道:

2005年4月27日星期三17:40:26 +1000,Michi Henning写道:
如果您正在编写所有托管代码,那么这个语句本质上就是它。


好​​吧,......不完全,但也许这就是为什么你写基本上?

在纯托管环境中,这不需要写析构函数。




析构函数可以非常有用,可以检查是否已对对象执行了适当的操作

。例如,您可以检查Dispose()

是否已被调用。


真正有用的信息:谁忘了给Dispose打电话,当然是

不可用,但我有一个很好的MixIn类,我可以用来记录

创建对象时的堆栈跟踪,这也很有用

有用知道。它给你一个开始寻找的地方。


虽然你可能无法用你的

终结器中的知识做很多事情,但你肯定会扔掉一个异常(程序* IS *

坏了,...它忘了调用Dispose()),运行时可能会执行

good-things用它。在调试模式下,Visual IDE弹出一个

消息框,提醒您一个否则会被忽视的错误。


您可以进行Dispose()调用" GC.SuppressFinalize(本)"并且避免

调用析构函数。


-

Helge Jensen

mailto:he **********@slog.dk

sip:他********** @ slog.dk

- => ;塞巴斯蒂安的封面音乐: http://ungdomshus.nu < = -


I''ve been having problem with destructors in the context of having ported C# code
developed under .NET to Mono. What happens is that, on a dual-CPU machine, various
parts of the code crash randomly (and rarely). This always happens during
process shutdown, after some thread has called System.Environment.Exit(). Clearly,
some sort of race condition.

Note that what follows only applies to destructors that are called when the process
shuts down -- there is no problem with destructors that get called by the GC when
the process is executing normally.

After a lot of debugging, I tracked it down to a destructor along the following
lines:

~SomeClass
{
if (!_destroyed)
{
System.Console.Error.WriteLine("Forgot to destroy SomeClass");
}
}

Under Mono, the attempt to write to the console crashes the process because, by the time
destructor is called by the garbage collector, the I/O subsystem has been partially
garbage collected already, and the process dies with a NullPointerException somewhere
in the guts of the I/O subsystem.

Not a problem with .NET: in .NET, the console isn''t destroyed until after everything
else is destroyed. (For details, see:
http://www.bluebytesoftware.com/blog...3-20c06ae539ae
There is a comment about two-thirds of the way down the page by Brian Grunkemeyer to that effect.)

Except that I can''t rely on this because, as far as I can see, the spec doesn''t guarantee that
the console will hang around during process shutdown, so it''s not portable code.

Then I started reading the spec a bit more and found that destruction order is not guaranteed and
that, if A refers to B, it''s entirely possible for B to be finalized before A. So, that got me to
thinking about what is actually legal to do from within a destructor, if that destructor may be
called during process shutdown. Here is a list of things that are *not* legal to do:

- I cannot dereference anything. If I do, the memory for the object that is reference is guaranteed
to still be there. However, that object may have been finalized already and, as a result, may no
long be in working order if I call a method on it.

- I cannot call a static method on anything. The static method itself is guaranteed to be there. However, the
implementation of the static method may depend on another static object that has been finalized already.
See previous point.

- I cannot safely invoke a virtual method on my own object. That''s because my own object may have a derived
part, and that derived part may have been finalized already, and the virtual method may end up using
something in the derived part that conceptually no longer exists.

So, that doesn''t leave a lot I can do in a destructor, as far as I can see. Here is what I can do safely:

I can assign or read any of my own data members, and any of the accessible data members of my base class.
That''s about it.

So, I can assign null to all my data members that have reference type, just to be nice to the GC. But that
really isn''t all that essential in most circumstances.

I can read my own data members. To what purpose? Well, to assert that my program state is still in fine
shape, of course:

~SomeClass()
{
System.Diagnostics.Assert(_myMember != null);
System.Diagnostics.Assert(_myOtherMember == null);
}

Oops. I can''t safely call a static method, because whatever the implementation of the static method uses may
have been finalized already. Just as I can''t safely write to the console, I can''t safely assert either.

Of course, I have no real control over when destructors are called. In particular, I have not control
over what destructors run when some thread calls Exit(). As a result, these restrictions apply to *all*
destructors, not just those of static objects.

Hmmm... That leaves destructors completely useless. There is nothing, not even asserting, that I can do safely.
Of course, that begs the question: why have destructors when I can''t do anything with them? As far as I can
see, the only legal statements inside a destructor are effectively no-ops.
Mystified,

Michi.

解决方案

It is recommended to avoid using finalizers (they are actually called
''finalizers'', not ''desctructors'') wherever possible. If you need to release
unmanaged resources, implement IDisposable and follow the IDisposable
implementation design pattern recommended by Microsoft. If your class does
not use anything to be disposed, do not use the finalizer at all.

Still, you can put the finalizer to a good use implementing the Microsoft''s
disposable object design pattern. In the pattern, the finalizer is the last
chance to release managed resources owned by the instance.

--
Sincerely,
Dmytro Lapshyn [Visual Developer - Visual C# MVP]
"Michi Henning" <mi***@zeroc.com> wrote in message
news:On**************@tk2msftngp13.phx.gbl...

I''ve been having problem with destructors in the context of having ported
C# code
developed under .NET to Mono. What happens is that, on a dual-CPU machine,
various
parts of the code crash randomly (and rarely). This always happens during
process shutdown, after some thread has called System.Environment.Exit().
Clearly,
some sort of race condition.

Note that what follows only applies to destructors that are called when
the process
shuts down -- there is no problem with destructors that get called by the
GC when
the process is executing normally.

After a lot of debugging, I tracked it down to a destructor along the
following
lines:

~SomeClass
{
if (!_destroyed)
{
System.Console.Error.WriteLine("Forgot to destroy SomeClass");
}
}

Under Mono, the attempt to write to the console crashes the process
because, by the time
destructor is called by the garbage collector, the I/O subsystem has been
partially
garbage collected already, and the process dies with a
NullPointerException somewhere
in the guts of the I/O subsystem.

Not a problem with .NET: in .NET, the console isn''t destroyed until after
everything
else is destroyed. (For details, see:
http://www.bluebytesoftware.com/blog...3-20c06ae539ae
There is a comment about two-thirds of the way down the page by Brian
Grunkemeyer to that effect.)

Except that I can''t rely on this because, as far as I can see, the spec
doesn''t guarantee that
the console will hang around during process shutdown, so it''s not portable
code.

Then I started reading the spec a bit more and found that destruction
order is not guaranteed and
that, if A refers to B, it''s entirely possible for B to be finalized
before A. So, that got me to
thinking about what is actually legal to do from within a destructor, if
that destructor may be
called during process shutdown. Here is a list of things that are *not*
legal to do:

- I cannot dereference anything. If I do, the memory for the object that
is reference is guaranteed
to still be there. However, that object may have been finalized already
and, as a result, may no
long be in working order if I call a method on it.

- I cannot call a static method on anything. The static method itself is
guaranteed to be there. However, the
implementation of the static method may depend on another static object
that has been finalized already.
See previous point.

- I cannot safely invoke a virtual method on my own object. That''s because
my own object may have a derived
part, and that derived part may have been finalized already, and the
virtual method may end up using
something in the derived part that conceptually no longer exists.

So, that doesn''t leave a lot I can do in a destructor, as far as I can
see. Here is what I can do safely:

I can assign or read any of my own data members, and any of the accessible
data members of my base class.
That''s about it.

So, I can assign null to all my data members that have reference type,
just to be nice to the GC. But that
really isn''t all that essential in most circumstances.

I can read my own data members. To what purpose? Well, to assert that my
program state is still in fine
shape, of course:

~SomeClass()
{
System.Diagnostics.Assert(_myMember != null);
System.Diagnostics.Assert(_myOtherMember == null);
}

Oops. I can''t safely call a static method, because whatever the
implementation of the static method uses may
have been finalized already. Just as I can''t safely write to the console,
I can''t safely assert either.

Of course, I have no real control over when destructors are called. In
particular, I have not control
over what destructors run when some thread calls Exit(). As a result,
these restrictions apply to *all*
destructors, not just those of static objects.

Hmmm... That leaves destructors completely useless. There is nothing, not
even asserting, that I can do safely.
Of course, that begs the question: why have destructors when I can''t do
anything with them? As far as I can
see, the only legal statements inside a destructor are effectively no-ops.
Mystified,

Michi.




On Wed, 27 Apr 2005 17:40:26 +1000, Michi Henning wrote:

Hmmm... That leaves destructors completely useless. There is nothing, not even asserting, that I can do safely.
Of course, that begs the question: why have destructors when I can''t do anything with them? As far as I can
see, the only legal statements inside a destructor are effectively no-ops.



If you are writing all managed code, then this statement is essentially it.
Within a pure managed environment this is no need to write a destructor.
Destructors are useful for releasing unmanaged resources. The garbage
collector has no specific knowledge on how to clean up an unmanaged
resource. Your class that encapsulates an unmanaged resource does have
that knowledge. So in your destructor you would take the responsibility
for making sure any unmanaged resources are properly released. Let the GC
handle your managed resources.

Also note that adding a destructor unnecessarily to an object can have a
negative impact on performance. An object with a destructor requires at
least two garbage collections. When the GC runs it reclaims the memory for
inaccessible objects without destructors. It cannot, at that time, collect
the inaccessible objects that do have destructors. Instead it places them
in a list of objects that are marked as ready for finalization. It then
calls the finalize method (destructor) on the objects in that list and then
removes them from the list. The next time GC runs it can them collect
these objects as they are now no longer in the list of objects that are
ready for finalization. So the fact that you have added a destructor to
your object only increases the chances that it will hang around longer than
other objects.
--
Tom Porterfield


Tom Porterfield wrote:

On Wed, 27 Apr 2005 17:40:26 +1000, Michi Henning wrote: If you are writing all managed code, then this statement is essentially it.
Well,... not quite, but perhaps that why you write "essentially"?
Within a pure managed environment this is no need to write a destructor.



Destructors can be quite usefull for checking that appropriate action
has been done on objects. For example you can check whether Dispose()
has been called.

The really usefull information: who forgot to call Dispose, is of course
not available, but I have a nice MixIn class that I can use to record
the stacktrace when the object is created and that is definatly also
usefull to know. It gives you a place to start looking.

While you may not be able to do much with the knowledge in your
finalizer, you can certainly throw an exception (the program *IS*
broken,... it forgot to call Dispose()) and the runtime may do
"good-things" with it. In debug mode the Visual IDE pops up a
messagebox, alerting you to a bug which would otherwise go unnoticed.

You can have your Dispose() call "GC.SuppressFinalize(this)" and avoid
invocation of the destructor.

--
Helge Jensen
mailto:he**********@slog.dk
sip:he**********@slog.dk
-=> Sebastian cover-music: http://ungdomshus.nu <=-


这篇关于析构函数没用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆