从方法返回并作为参数传递时结构的预期寿命是多少?

时间:2011-11-28 18:41:38

标签: c# .net clr

我将尝试用一个例子来更多地理解这个问题:

我有一个结构:

struct RefStruct
{
   public object token;
   public object item;
}

我有一个方法返回struct:

RefStruct createItem() {... }

我们假设'createItem'生成一个项目和一个令牌,其中所述令牌包含'item'和'item'可用的信息,通过WeakReference在内部引用相同的令牌。

现在,如果我调用此代码(假设'doSomething'处理一个Item并要求令牌处于活动状态):

{
   ...
   doSomething(createItem().item);
   ...
}

- 请注意,呼叫接收'item'而不是整个struct。

在'doSomething'调用期间,保证由'createItem'重新生成的结果RefStruct保留在内存中吗?或者是CLR丢弃了引用,现在只引用了一个项目(允许临时结构被GC)?

希望这足够了; p

4 个答案:

答案 0 :(得分:11)

  

我们假设'createItem'生成一个项目和一个令牌,其中所述令牌需要保持活跃(即没有GC)以使'item'有效。

您的设计从根本上被打破。如果令牌必须处于活动状态以使该项有效,则该项应存储对该令牌的引用。

修复您的设计。要么将项目的有效性与令牌的生命周期分离,要么使项目保持在令牌上,或者使第三项内容保持在令牌上。 (然后保持第三件事。)

  

在调用'doSomething'期间,是否保证由'createItem'重新生成的结果RefStruct保留在内存中?

绝对不是。垃圾收集器不仅在调用DoSomething 之前收集令牌,而且实际上在CreateItem返回之前收集 。允许垃圾收集器在未来任意远远地看并预测程序的未来行为。如果它可以通过分析确定令牌对象从未访问,则允许在创建之后将其释放

在CLR的任何实现中是否实际执行此操作都是一个实现细节,可能会有所变化。

  

如果我将'CreateItem'的结果存储到临时结构中,并将'item'属性传递给'doSomething',优化器仍然可以提前释放该标记吗?

是。 jit编译器完全有权确定从未访问过现在被提升为局部变量的结构的标记字段;垃圾收集令牌引用的权利完全在其范围内。

struct storage 本身不会提前回收;例如,您可以稍后到本地存储的令牌字段部分。但是标记字段引用的对象的内存可以随时回收。

  

使用'token'成员上的volatile会改变这个吗?

没有。易失性字段在写入和读取时具有获取和释放语义;这与该领域的生命没有任何关系。

你能解释一下为什么你认为让这个领域变得不稳定会改变它的生命周期吗?我总是有兴趣了解为什么人们会相信奇怪的事物。

  

我想保证,只要我拿着令牌,它就不会被GC,在'doSomething'操作期间避免使用GC

现在我们来看实际问题。到目前为止,您一直在询问有关钻孔马达如何工作的问题,而不是询问您想要钻孔的类型。如果问题是“如何让这个死的东西活着?”答案很简单。 致电GC.KeepAlive这就是它的用途。

正如文件明确指出:

  

KeepAlive方法的目的是确保存在对可能被垃圾收集器过早回收的对象的引用。

现在,一个合理的问题是“但如果令牌有被收集的危险,那么有人可能会如何使用它来提高物品的效率?”您在此评论中回答了这个问题:

  

这是缓存系统的一部分,并且strcut中的'token'是一个强引用,还有其他WeakReferences,而没有收集令牌,已知缓存项是必需的,不应该从高速缓存

您正在让垃圾收集器负责执行您的缓存策略。垃圾收集器的设计不适合 场景;垃圾收集器被设计成对通用编程语言中的内存管理的一般问题具有良好的行为。

在我设置由一群人设计的终身管理引擎来解决他们负责确定可能完全不同的问题空间的策略的一般问题之前,我会认真思考。您提出这些问题这一事实表明您需要构建自己的缓存策略实施代码,而不是依赖GC以您不喜欢的方式为您执行此操作。

Supercat问道:

  

如果将RefStruct替换为类,会不会出现完全相同的问题?

是。如果你这样做,那么:

Tuple<object, object> holder = CreateItem(...); 
DoSomething(holder.Item1);

然后再次,GC完全有权确定“持有者”已经死亡,因此holder.Item2已经死亡,即CreateItem执行完毕的那一刻。正如我所说,你必须保持“持有人” - “第三件事” - 活着。允许GC确定广泛的格局以确定什么仍然存在。

答案 1 :(得分:5)

结构没有生命;持有结构的东西都有生命周期 (虽然盒装结构确实有生命周期)

您的令牌可以立即进行GC,因为它从未被引用过。

答案 2 :(得分:1)

不,RefStruct可以立即GCed。即使这样做:

var rs = createItem();
doSomething(rs.item);

不保证令牌保持活跃。

您可以考虑实现IDisposable以使对令牌的依赖更加明确(在这种情况下,建议使用类而不是结构,请参阅this article on MSDN

class RefStruct : IDisposable
{
   public object token;
   public object item;

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

   protected virtual void Dispose(bool disposing)
   {
        if (disposing)
        {
            token = null; // not necessary technically
        }
    }
}

然后使用它:

using (var rs = createItem())
{
    doSomething(rs.Item);
}

答案 3 :(得分:1)

可能有一些合理的理由让RefStruct成为结构而不是类,但无论它是结构还是类,都必须进行GC.KeepAlive调用以确保不会收集令牌而其他代码在Item上运行。我建议有人可能想要,而不是直接暴露Item,有一个通用的DoSomethingWithItem方法接受委托和泛型ref参数,它将调用Item上提供的委托,然后GC.KeepAlive令牌。在大多数使用场景中,让泛型方法接受泛型ref参数将允许使用静态委托并将任何必要的输入或输出参数放入值类型中,从而避免GC压力。