C# GC.KeepAlive()当它未注释时,仍然只有一个实例,如何?
对于下面的引用线程,即使我对GC.KeepAlive()进行注释,也没有发现任何差异,它会阻止任何其他实例的创建。为什么作者明确提到它的重要路线C# GC.KeepAlive()当它未注释时,仍然只有一个实例,如何?,c#,.net,winforms,c#-4.0,instance,C#,.net,Winforms,C# 4.0,Instance,对于下面的引用线程,即使我对GC.KeepAlive()进行注释,也没有发现任何差异,它会阻止任何其他实例的创建。为什么作者明确提到它的重要路线 如果不这样做,互斥锁将在垃圾收集时被销毁,但这不是保证立即发生的事件,这就是为什么它仍然可以长时间工作的原因 我会用一个静态的我自己看看第二个答案。没有多少测试可以证明GC.KeepAlive是多余的,但只有一个(失败的)测试可以证明它是必要的 换句话说,如果省略了GC.KeepAlive,则代码可能无法正常工作;不能保证它会立即破裂。而且它可能无法正
如果不这样做,互斥锁将在垃圾收集时被销毁,但这不是保证立即发生的事件,这就是为什么它仍然可以长时间工作的原因
我会用一个静态的我自己看看第二个答案。没有多少测试可以证明
GC.KeepAlive
是多余的,但只有一个(失败的)测试可以证明它是必要的
换句话说,如果省略了GC.KeepAlive
,则代码可能无法正常工作;不能保证它会立即破裂。而且它可能无法正常工作,因为mutex
是超出范围的局部变量;这使它有资格进行垃圾收集。如果GC决定收集该对象,互斥锁将被释放(并且您将能够启动一个新实例)。更新:这个问题是;关于这个问题的更多想法,请参阅那篇文章。谢谢你的提问
让我澄清一下这里发生了什么。让我们忽略它是互斥体的事实,并考虑一般情况:
class Foo
{
public Foo()
{
this.x = whatever;
this.y = whatever;
SideEffects.Alpha(); // Does not use "this"
}
~Foo()
{
SideEffects.Charlie();
}
...
}
static class SideEffects
{
public static void Alpha() {...}
public static void Bravo() {...}
public static void Charlie() {...}
public static void M()
{
Foo foo = new Foo(); // Allocating Foo has side effect Alpha
Bravo();
// Foo's destructor has side effect Charlie
}
}
M
的作者希望副作用Alpha()
,Bravo()
和Charlie()
按顺序发生。现在,您可能会认为Alpha()
必须在Bravo()
之前发生,因为Alpha()
和Bravo()
发生在同一线程上,而C#保证在同一线程上发生的事情保留了副作用的顺序。你是对的
您可能会认为Charlie()
必须在Bravo()
之后发生,因为Charlie()
在foo
中存储的引用被垃圾收集之前不会发生,而局部变量foo
会在调用Bravo()之后将该引用保持活动状态,直到控件离开foo
的范围
这是错误的。允许C#编译器和CLR jit编译器一起工作,以允许局部变量提前声明为“死”。记住,C#只能保证在从一个线程观察时,事情以可预测的顺序发生。垃圾收集器和终结器在它们自己的线程上运行!因此,垃圾收集器可以合法地推断foo
——在调用Bravo()
之前,Bravo()中未使用的foo
——已死亡,因此副作用Charlie()
可能发生在另一个线程上的Bravo()
——或Bravo()
期间
KeepAlive(foo)
会阻止编译器进行这种优化;它告诉垃圾收集器,foo
至少在KeepAlive
之前是活动的,因此终结器中的副作用Charlie()
保证在副作用Bravo()
之后出现
现在有一个有趣的问题。如果没有keepalive,副作用会在Alpha()
之前发生吗?事实证明,在某些情况下是的!允许抖动非常剧烈;如果发现ctor的this
将在ctor结束后立即失效,则允许在ctor停止对this
的字段进行变异后,安排this
进行收集
除非您在Foo()
构造函数中执行KeepAlive(this)
,否则允许垃圾收集器在Alpha()
运行之前收集this
!(当然,前提是没有其他东西使其保持活动状态。)对象可能会在其构造函数仍在另一个线程上运行时被终止。这也是您需要非常小心地编写具有析构函数的类的另一个原因。整个对象需要设计为在另一个线程上突然调用析构函数时具有健壮性,因为抖动是攻击性的
在您的特定情况下,“Alpha”是删除互斥的副作用,“Bravo”是运行整个程序的副作用,“Charlie”是释放互斥的副作用。如果没有keepalive,则允许在程序运行之前释放互斥锁,或者更可能在程序运行时释放互斥锁。它不需要发布,而且大部分时间不会发布。但是,如果jitter决定在倒垃圾时变得咄咄逼人的话,这可能是真的
有哪些替代方案
keepalive是正确的,但就个人而言,我不会在原始代码中选择keepalive,它基本上是:
static void Main()
{
Mutex m = GetMutex();
Program.Run();
GC.KeepAlive(m);
}
另一种选择是:
static Mutex m;
static void Main()
{
m = GetMutex();
Program.Run();
}
抖动允许提前终止局部变量,但不允许提前终止静态变量。因此,无需保留
更好的做法是利用互斥是一次性的这一事实:
static void Main()
{
using(GetMutex())
Program.Run();
}
这是一个简短的写作方式:
static void Main()
{
Mutex m = GetMutex();
try
{
Program.Run();
}
finally
{
((IDisposable)m).Dispose();
}
}
现在,垃圾收集器无法提前清理互斥体,因为它需要在控件离开运行后进行处理。(如果抖动可以证明处理互斥锁不起任何作用,则允许提前清理互斥锁,但在这种情况下,处理互斥锁会产生影响。)我猜是因为在您的应用程序中,它没有被垃圾收集。它用于阻止对互斥对象进行垃圾收集。您需要运行程序,以便调用垃圾收集器,然后查看这是否会影响您的应用程序。@TheKingDave:噢,好的,谢谢。但是,如果我只是使用这些代码,并且想作为一个新手得出一个学习结论,那么VS会让我感到困惑。谢谢,你所说的“我会使用一个静态的我自己看到第二个答案”是什么意思?链接的问题的答案低于公认的答案,它使用了一个静态变量