Process.HasExited 竞争条件

Process.HasExited race condition

我有一个 System.Diagnostics.Process 的实例,它是通过 Process.GetProcessesByName 创建的。

成功打开进程后,我执行各种操作,例如读取其内存和window标题。

此操作基于计时器不断执行,我的意思是 Timer.Elapsed 事件处理程序是流程操作的来源。

现在,我注意到我有一个我无法使用我所知道的任何方法解决的竞争条件。事情是这样发生的:

timerElapsedEvent(...) {

    if (!process.HasExited) {

        process.Refresh(); // Update title.
        var title = process.MainWindowTitle;
    }

}

如果进程是 运行,并且我的代码进入了 if 块,进程有可能在执行 process.MainWindowTitle 调用之前退出,这会导致一个例外。

我需要的是一种以某种方式捕获进程的退出事件并使其保持活动状态直到可以安全关闭它而不会导致正在监视它的应用程序崩溃的方法,从而确保它会在关闭 之前等待 process.MainWindowTitle(或可以解决此问题的任何其他解决方案)。

而且,同时,另一个方法可能是运行一个ReadProcessMemory,它也会崩溃。

我该如何解决这个问题?

PS: Process.Exit 事件处理程序不起作用,因为它不会在 process.MainWindowTitle 之前触发,它只会在当前指令完成后触发。 我很确定以某种方式控制退出事件是解决此问题的唯一方法,因为 HasExit 可能随时更改,在实际调用进程上的方法之前我有多少检查。

PS2:我刚刚意识到这是一个TOCTTOU案例,除非我能控制我打开的过程,否则这是无法解决的,所以我把它留在这里只是为了看看是否有人知道如何做那。

简短版本:你不能。

这里有一个基本的 "time-of-check-to-time-of-use" 问题,您没有足够的控制权来解决。 OS 总是能够在您检查 HasExited 属性 和你检查 MainWindowTitle 属性.

Process class 对强制获取异常没有多大作用,但它已经足够了。特别是,调用 Refresh() 会强制 class 向 "forget" 它所知道的有关该进程的任何信息,以便它会在您再次请求时重新检索信息。这包括进程的主要 window 句柄。

Process class 使用本机 window 枚举函数搜索已知进程 ID 的 window 句柄。由于进程已经退出,它无法找到句柄,returning 一个 NULL 值(IntPtr.Zero 在托管术语中)。在看到空 return 值时,Process class 强制调用 InvalidOperationException


唯一可靠的解决方案是始终准备好捕获异常。在检查状态和尝试做一些依赖它的事情之间,总是有机会改变状态。


在学术方面,我发现有趣的是,如果您设置 EnableRaisingEvents 属性,Process class 可以(并且通常)更有效地检测退出处理并抛出异常。

特别是,当 EnableRaisingEvents 属性 被设置时, Process class 寄存器被 OS 通知(通过线程池的RegisterWaitForSingleObject() 方法)当进程句柄发出信号时。 IE。在这种情况下,Process class 甚至不需要搜索主 window 句柄,因为如果进程退出,它几乎会立即得到通知。

(当然,在非常小的 window 机会中仍然存在潜在的内部竞争条件,因为当 Process class 检查时通知可能尚未到达对于 has-exited 状态,但是在 Process class 枚举 windows).

之前进程可能仍然已经退出

总之,这最后一点不影响基本答案;这只是我在 the Process source code 闲逛时学到并发现有趣的一些琐事。 :)