C# 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
是多余的,但只有一个(失败的)测试可以证明它是必要的

换句话说,如果省略了
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会让我感到困惑。谢谢,你所说的“我会使用一个静态的我自己看到第二个答案”是什么意思?链接的问题的答案低于公认的答案,它使用了一个静态变量