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

本文关键字:多少 结构体 返回 方法 参数传递 | 更新日期: 2023-09-27 18:15:53

我将试着用一个例子让这个问题更有意义:

我有一个结构体:
struct RefStruct
{
   public object token;
   public object item;
}

我有一个方法返回结构体:

RefStruct createItem() {... }

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

现在,如果我调用这段代码(假设'doSomething'处理一个项目,并要求令牌是活的):

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

-请注意,调用接收的是'item'而不是整个结构体。

是否保证由'createItem'返回的结果RefStruct在调用'doSomething'期间保存在内存中?或者是被CLR丢弃的引用,现在只有项被引用(允许临时结构被GCed)?

p

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

让我们假设'createItem'生成一个项目和一个令牌,其中所述令牌需要保持存活(即不是GCed)才能使'item'有效。

你的设计根本就坏了。如果令牌必须是有效的,则该项应该存储对令牌的引用。

修复你的设计。要么将项目的有效性与令牌的生命周期解耦,要么让项目持有令牌,要么让第三个东西同时持有它们。(然后让第三件事保持活力。)

是否保证由'createItem'返回的结果RefStruct在调用'doSomething'期间保存在内存中?

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

在CLR的任何实现中是否实际这样做是一个实现细节,可能会发生更改。

如果我将'CreateItem'的结果存储到临时结构体,并将'item'属性传递给'doSomething',优化器是否仍然允许提前释放令牌?

是的。jit编译器完全有权利决定现在被提升为局部变量的结构体的token字段永远不会被访问;它完全有权对令牌引用进行垃圾收集。

结构体storage本身不会被提前回收;例如,您可以稍后将写入本地存储的令牌字段部分。但是令牌字段所引用的对象的内存可以随时回收。

在'token'成员上使用volatile会改变这一点吗?

。Volatile字段在写和读时有获取和释放语义;这与该领域的寿命没有任何关系。

你能解释一下为什么你认为使字段易失性会改变它的生命周期吗?我总是有兴趣了解为什么人们相信奇怪的事情。

我想保证只要我持有令牌,它就不会被GCed,以避免在'doSomething'操作期间进行GC

现在我们来讨论实际的问题。到目前为止,你一直在问钻机马达是如何工作的,而不是问你想钻什么样的洞。如果问题是"我如何让这个死去的东西活下去?"答案很简单。 GC.KeepAlive打电话。这就是它的作用。

正如文档明确指出的:

KeepAlive方法的目的是确保存在对一个对象的引用,该对象有被垃圾收集器过早回收的风险。

现在,一个合理的问题是"但是如果代币有被收集的危险,那么怎么可能有人使用它来提高物品的效率呢?"你可以在下面的评论中回答这个问题:

这是缓存系统的一部分,因为strcut中的'token'是一个强引用,所以还有其他的WeakReferences,虽然token不被收集,但已知缓存项是需要的,不应该从缓存中删除

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

在我把一个由一群人设计的、用来解决他们一般问题的生命周期管理引擎放在一个可能完全不同的问题空间中负责确定策略之前,我会非常仔细地考虑。你问这些问题的事实表明,你需要构建你自己的缓存策略执行代码,而不是依靠GC以一种你不喜欢的方式为你做这件事。

Supercat问道:

如果用类替换RefStruct,不会发生完全相同的问题吗?

是的。如果是这样,请这样写:

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

然后,GC完全有权利确定"持有者"已经死亡,因此是持有者。Item2是死的,CreateItem完成执行的那一刻。正如我所说,你必须让"持有人"——"第三件事"——保持活力。

struct没有生命周期;保存结构的对象有生命周期。
(尽管盒装结构体有生命周期)

你的令牌可以立即GC,因为它永远不会被引用。

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

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

不能保证令牌是有效的。

您可以考虑实现IDisposable,使对令牌的依赖更显式一些(在这种情况下,确实建议使用类而不是结构体,请参阅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);
}

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