C# WeakReference object is NULL in finalizer although still strongly referenced

时间秒杀一切 提交于 2019-11-29 05:11:06

A sometimes-irksome limitation of WeakReference is that a WeakReference may be invalidated if if no strongly-rooted reference exists to the WeakReference itself, and this may occur even if the trackResurrection constructor parameter was true, and even if the target of the WeakReference is strongly rooted. This behavior stems from the fact that a WeakReference has an unmanaged resource (a GC handle) and if the finalizer for the WeakReference didn't clean up the GC handle, it would never get cleaned up and would constitute a memory leak.

If it will be necessary for an object's finalizers to make use of WeakReference objects, the object must make some provision to ensure that those objects remain strongly referenced. I'm not sure what the best pattern is to accomplish this, but the ConditionalWeakTable<TKey,TValue> that was added in .net 4.0 may be useful. It's a little bit like Dictionary<TKey,TValue> except that as long as a the table itself is strongly referenced and a given key is strongly referenced, its corresponding value will be regarded as strongly referenced. Note that if a ConditionalWeakTable holds an entry linking X to Y, and Y to the table, then as long as X or Y remains, the table will remain as well.

There are two aspects of garbage collection that you didn't count on:

  • The exact time at which the WeakReference.IsAlive becomes false. Your code implicitly assumes that will happen when the finalizer runs. This is not the case, it happens when the object gets garbage collected. After which the object is placed on the finalizer queue, because it has a finalizer and GC.SuppressFinalize() wasn't called, waiting for the finalizer thread to do its job. So there's a period of time where IsAlive is false but ~B() hasn't run yet.

  • The order in which objects get finalized is not predictable. You implicitly assume that B is finalized before A. You cannot make this assumption.

There's also a bug in the B.Dispose() method, it won't correctly count B instances when the client code explicitly disposed the object. You haven't hit that bug yet.

There is no reasonable way to fix this code. Moreover, it tests something that is already backed by hard guarantees provided by the CLR. Just remove it.

The WeakReference _weakB is available for garbage collection at the same time as the object a is. You don't have a guarantee of order here, so it could very well be that _weakB is finalized before object a.

Accessing _weakB in the finalizer of A is dangerous, since you don't know the state of _weakB. I'm guessing that in your case it has been finalized, and that that is causing it to return null for .Target.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!