事件 Process.Exited 在 while 循环中重新实例化后超过其对象寿命

Event Process.Exited outlives its object life after re-instantiation in a while loop

我在我的代码中检测到某种我无法解释的行为,即使它工作正常,但不完全理解正在发生的事情确实困扰着我。 下面的脚本创建 运行s n_total 个记事本进程,但每次最多只允许 n_cpus 个记事本 运行ning。一开始,n_cpus 进程启动,其余进程仅在一个或多个 运行ning 记事本终止时才开始。每个记事本进程都可以由用户简单地通过关闭它的 window 来终止,这会触发代码中的事件 Process.Exited。现在,在循环中,变量 p 被重新用于实例化 class 过程,每次需要一个新的记事本并且每个对象 p 订阅事件 Process.Exited通过 p.Exited += p_Exited; 考虑到我们有 n_cpus = 3 和 运行 代码,直到它同时生成这 3 个记事本。我希望只有最后一个实例 p 会触发事件,因为我正在重新使用 p 并且 p.Exited 属于该对象,但是不...无论我使用什么记事本关闭,事件被触发并出现一个新的记事本。这是怎么回事?是否有某种无对象的委托 EventHandler 列表可以记住我创建的每个进程?

using System;
using System.Diagnostics;
using System.Threading;

class Program
{
    // Summary: generates processes for "n_total" notepads allowing for a maximum of "n_cpus"
    //          notepads at each time. Every time a notepad closes another appears until all are run.
    //          A single variable "p" instantiates the class "Process" and the event "p.Exited"
    //          updates the number of running processes "n_running".

    static int n_running = 0; // number of notepads running each time
    static void Main()
    {
        int n_cpus = 3;
        int n_total = 3 * n_cpus;
        int i_run = 0;

        while (i_run < n_total) // Process generating routine until all are run
        {
            if (n_running < n_cpus) // Only a maximum of n_cpus running at each time
            {
                n_running++;
                i_run++;

                Process p = new Process(); // A new object per process
                p.StartInfo.FileName = "cmd.exe";
                p.StartInfo.Arguments = "/c notepad.exe";
                p.StartInfo.UseShellExecute = false;
                p.StartInfo.CreateNoWindow = true;
                p.EnableRaisingEvents = true;
                p.Exited += p_Exited; // Is this associated with a particular object "p", right?
                p.Start();
            }
            else Thread.Sleep(1000); // Waits 1s before checking for new terminated processes
        }
    }

    static private void p_Exited(object sender, EventArgs e)
    {
        n_running--; // Updates the number of active processes. Triggers new future processes
    }
}

OS 似乎可以为您做到这一点,因为您启用了通过 EnableRaisingEvents = true 引发事件。

参见:

我认为您可能在这里做出了一些错误的假设:

变量不是对象,它们是对象的引用。当您将对象分配给变量(通过 new 或其他方式)时,您不是 "replacing" 或以其他方式删除或删除该变量先前引用的对象。如果没有更多对前一个对象的引用,它 可能 在某个时间 被垃圾收集,但该对象仍然存在并且可能有其他东西保留它 "alive" (通过包含对它的引用)。一些编译器优化甚至可能导致相反的情况发生:如果编译器确定该变量不再被使用,则该对象可以在引用它的变量超出范围之前被垃圾收集。

事件模式可能是 C# 中 "memory leaks" 的最大单一来源,因为许多开发人员没有意识到,当您注册一个事件时,被侦听对象的生命周期现在扩展到侦听器(因为被侦听的对象现在具有对侦听器的引用,此引用导致垃圾收集器不收集该对象)。由于您的 p_Exited 是静态的,因此您创建的所有 Process 对象现在都是 "rooted"(不会被垃圾回收),直到您取消注册该事件。

the variable p is re-used to instantiate the class Process

你甚至不是 "reusing" p 变量。它是在循环范围内声明的,所以每次循环,p实际上是一个"brand new"变量。当涉及到闭包时,这成为一个特别重要的区别(即使是 C# 语言团队 got this wrong)。