我需要固定匿名代表吗?
Posted
技术标签:
【中文标题】我需要固定匿名代表吗?【英文标题】:Do I need to pin an anonymous delegate? 【发布时间】:2011-07-24 19:36:54 【问题描述】:我正在从一个 C# 应用程序调用 CopyFileEx,并将一个匿名委托传递给 LPPROGRESS_ROUTINE 参数,以获取有关文件复制进度的通知。
我的问题是,匿名代表是否需要固定以及为什么(或为什么不固定)。
此外,如果出现以下情况,答案是否会改变:
-
CopyFileEx 未阻止。
如果我传入了一个非匿名的委托。
谢谢!
【问题讨论】:
【参考方案1】:委托不需要固定。如果垃圾收集器无法移动托管对象,则它被固定。如果编组信息正确,则编组层将确保传递指向不可移动对象的指针。
但是,您在上面建议局部变量可能保持委托活动的评论表明对变量生命周期的误解。我请您参考规范,其中指出:
局部变量的实际生命周期取决于实现。例如,编译器可能静态确定块中的局部变量仅用于该块的一小部分。使用这种分析,编译器可以生成导致变量存储的生命周期比其包含块更短的代码。 局部引用变量所引用的存储空间的回收与该局部引用变量的生命周期无关
换句话说,如果你说:
void M()
Foo foo = GetAFoo();
UnmanagedLibrary.DoSomethingToFoo(foo);
然后允许抖动说“你知道,我看到没有托管代码在调用非托管调用后再次使用 foo;因此我可以积极地从另一个线程回收该对象的存储 /em> 当时”。这意味着当突然在另一个线程上释放对象时,非托管调用可以在对象上工作。
如果 Foo 有析构函数,这尤其令人讨厌。当对象被非托管库使用时,终结代码可能会在另一个线程上运行,天知道会导致什么样的灾难。
在这种情况下,您需要使用 KeepAlive 来保持托管对象的活动状态。 不要依赖局部变量;局部变量被特别记录为不保证让事物保持活力。
更多详情请见http://msdn.microsoft.com/en-us/library/system.gc.keepalive.aspx。
【讨论】:
谢谢埃里克。我有点困惑,因为 Chris Brumme 表示“PInvoke 要么将您的数据复制到 GC 堆外的固定内存,要么将内存固定在 GC 堆中并将这些字节直接暴露给非托管代码。在任何一种情况下,你都不会”不需要显式地固定——只要对这些字节的访问范围在 PInvoke 调用的持续时间内。”在blogs.msdn.com/b/cbrumme/archive/2003/05/06/… 中——这不是也与代表有关吗?我假设如果 pinvoke 层固定数据,GC 将不会收集... @SpeksETC:您链接到的文章明确指出您的假设是错误的。它说“但是,应用程序负责以某种方式延长委托的生命周期,直到不再发生来自非托管代码的调用。” @Eric Lippert:该语句不能仅指异步非托管调用吗?似乎否定了他在 cmets 中写的“如果非托管被调用者只需要在调用期间访问缓冲区,那么 PInvoke 编组层通常会在这段时间内将其固定”,除非编组层以不同方式对待委托。 . @Eric,KeepAlive 文档说 KeepAlive 方法会保持引用直到调用结束。我会假设(错误地?)P/Invoke 方法调用在这方面不会是特殊的,并且在调用结束之前也保持引用。如果这确实是真的(与您发布的内容不同),那么 KeepAlive 主要只在回调场景中需要,例如 KeepAlive 示例中的示例。我知道 GC 对当地人非常咄咄逼人,但认为它没有你写的那么远。您能否进一步详细说明这一点? @Jeffrey,你是对的。看起来我阅读文档太快了......但我的直觉是 P/Invoke 自动固定/保持对参数的引用,直到调用结束,回调需要 GC.KeepAlive。这基本上可以解释为什么 KeepAlive 似乎只与委托和/或异步的东西一起被提及——而不是像byte[]
这样的数组。有任何相反的明确信息吗?【参考方案2】:
您不需要固定它,但只要复制正在进行,您就需要保持对它的引用。
非托管代码调用的 thunk 已固定,但您必须确保委托没有被垃圾回收 - 因此引用。
【讨论】:
如果匿名方法是一个局部变量,只要 CopyFileEx 没有返回,它是否被认为是“活动的”? @sehe, @SpeksETC:很确定,是吗?规范中究竟是在哪里说的?我的规范副本说相反,即“编译器可以生成导致变量存储的生命周期比其包含块短的代码。” 谢谢 - 这正是我所关心的(当一个实例被 GC'd 并最终确定,而它的一个方法正在执行过程中时,我看到了这一点)。我刚刚阅读了blogs.msdn.com/b/cbrumme/archive/2003/05/06/…,如果我理解正确的话,pinvoke 层似乎会在通话的整个生命周期内为您进行固定,所以在这种情况下,它似乎被覆盖了...... @sehe - 否。确定变量实际生命周期的机制可能假定被调用方法中的参数本身将在必要时持有对托管对象的引用。如果参数变量在被调用的方法中快速设置为 null(或其他值),这可能是一个值得优化的优化,在这种情况下,托管对象没有引用并且可以安全地收集,即使被调用的方法尚未返回. @sehe:杰弗里是正确的。将引用作为参数传递并不必然延长任何事物的生命周期。如果将死对象传递给不使用它的方法,它仍然是死对象,因此可以随时回收。从托管运行时的角度来看,非托管函数不使用托管对象。如果你想告诉运行时一个非托管函数使用一个托管对象,你有责任让它保持活动状态。这就是 unmanaged 的意思——如果你想调用不管理对象生命周期的代码,你可以这样做。【参考方案3】:从下面的msdn 看来,在这种情况下,pinning 和 GC.KeepAlive 都不需要,因为 CopyFileEx 是同步的。具体来说:
“通常,您不必担心委托的生命周期。每当您将委托传递给非托管代码时,CLR 将确保委托在调用期间处于活动状态。但是,如果本机代码保留一个超出调用范围的指针副本并打算稍后通过该指针回调,您可能需要使用 GCHandle 显式阻止垃圾收集器收集委托。"
由于 CopyFileEx 不会在调用范围之外保留指向函数的指针,因此我们不需要调用 KeepAlive。
【讨论】:
这是不正确的,即使在这种情况下遇到问题的可能性“非常小”。正如其他答案中所指出的,GC - 在另一个线程上 - 可能 在调用开始后在任何时候 回收作为参数提供的委托。对于短暂的同步调用,这是“通常,您不必担心”,但保证它会起作用的唯一方法是确保 GC 获胜t 通过使用 KeepAlive 或以其他方式维护强引用来回收它。 换一种说法:如果同步调用持续几分钟,您是否会感觉不使用 KeepAlive?我肯定不会,并且已经被这种“通常”而不是“总是”所困扰;甚至几秒钟对 CPU 来说也是亿万年,而 GC 有足够的空闲时间变得太饿了。确保完全按照要求控制生命周期。以上是关于我需要固定匿名代表吗?的主要内容,如果未能解决你的问题,请参考以下文章