Eri*_*ert 12
更新:这个问题是我2013年6月的博客主题 ; 有关此主题的更多想法,请参阅该文章.谢谢你这个好问题!
让我澄清一下这里发生了什么.让我们忽略它是互斥的事实并考虑一般情况:
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
}
}
Run Code Online (Sandbox Code Playgroud)
M欲望的作者,副作用Alpha(),Bravo()并按Charlie()顺序发生.现在,您可能会认为Alpha()必须在之前发生Bravo()因为Alpha()并且Bravo()发生在同一个线程上,并且C#保证在同一个线程上发生的事情保留了副作用的顺序.你是对的.
您可能之所以Charlie()必须发生这种情况,Bravo()因为Charlie()直到存储的引用foo被垃圾收集才会发生,并且局部变量foo会保持该引用处于活动状态,直到控制foo在调用之后离开范围Bravo(). 这是错的.允许C#编译器和CLR jit编译器一起工作,以允许将局部变量提前声明为"死".请记住,C#仅保证在从一个线程观察时,事物以可预测的顺序发生.垃圾收集器和终结器在自己的线程上运行!因此,垃圾收集器推断- 在调用之前死亡 - 并且因此副作用可能在另一个线程之前或期间发生,这是合法的.fooBravo()Bravo()Charlie()Bravo() Bravo()
A KeepAlive(foo)会阻止编译器进行优化; 它告诉垃圾收集器foo至少存在至少KeepAlive,因此Charlie()终结器中的副作用保证在副作用之后Bravo().
现在这是一个引人入胜的问题.如果没有存活,可副作用Charlie()发生之前Alpha()?事实证明,在某些情况下是的!抖动被允许非常激进; 如果它发现thisctor一旦ctor结束就会死掉那么它就会被允许this在ctor停止变异之后的那一刻安排收集this.
除非你KeepAlive(this)在Foo()构造函数中执行,否则允许垃圾收集器this在Alpha()运行之前收集!(当然没有提供任何其他东西使它保持活着.)一个对象可以在其构造函数仍然在另一个线程上运行时完成. 这是你需要非常小心地编写具有析构函数的类的另一个原因.整个对象需要设计为在析构函数突然被另一个线程意外调用时面对强大,因为抖动是激进的.
在您的特定情况下,"Alpha"是取出互斥锁的副作用,"Bravo"是运行整个程序的副作用,而"Charlie"是释放互斥锁的副作用.如果没有keepalive,则允许在程序运行之前释放互斥锁,或者更可能在运行时释放互斥锁.它不需要被释放,并且大多数时间它不会被释放.但它可能是,如果抖动决定采取积极措施取出垃圾.
有哪些替代方案?
keepalive是正确的,但我个人不会在原始代码中选择keepalive,基本上是:
static void Main()
{
Mutex m = GetMutex();
Program.Run();
GC.KeepAlive(m);
}
Run Code Online (Sandbox Code Playgroud)
另一种选择是:
static Mutex m;
static void Main()
{
m = GetMutex();
Program.Run();
}
Run Code Online (Sandbox Code Playgroud)
抖动允许早期杀灭局部变量,但不会允许早期杀死静态变量.因此,不需要KeepAlive.
更好的是利用Mutex是一次性的这一事实:
static void Main()
{
using(GetMutex())
Program.Run();
}
Run Code Online (Sandbox Code Playgroud)
这是一个简短的写作方式:
static void Main()
{
Mutex m = GetMutex();
try
{
Program.Run();
}
finally
{
((IDisposable)m).Dispose();
}
}
Run Code Online (Sandbox Code Playgroud)
而现在垃圾收集器无法提前清理互斥锁,因为它需要在控制离开后进行处理Run.(如果抖动可以证明处理互斥锁什么都不做,那么就可以提前清理它,但在这种情况下,配置互斥锁会产生影响.)