我有一个System.Diagnostics.Process
的实例,它是通过Process.GetProcessesByName
创建的。
在我成功打开进程后,我执行各种操作,例如读取内存和窗口标题。
此操作基于计时器不断执行,我的意思是Timer.Elapsed
事件处理程序是进程操作的源。
现在,我注意到我的竞争条件是我无法用我所知道的任何东西来解决的。以下是它的发生方式:
timerElapsedEvent(...) {
if (!process.HasExited) {
process.Refresh(); // Update title.
var title = process.MainWindowTitle;
}
}
如果进程正在运行,并且我的代码进入if
块,则在执行process.MainWindowTitle
调用之前进程可能会退出的可能性很小,这会导致异常。
我需要的是一种以某种方式捕获进程的退出事件并使其保持活动状态的方法,直到可以安全地关闭它而不会使正在监视它的应用程序崩溃,从而确保它将等待{{ 1}}关闭之前(或任何其他解决此问题的解决方案)。
此外,与此同时,另一种方法可能正在运行process.MainWindowTitle
,这也会崩溃。
我该如何解决这个问题?
PS:Process.Exit事件处理程序不起作用,因为它不会在ReadProcessMemory
之前被触发,它只会在当前指令完成后触发。
我非常确定以某种方式控制退出事件是解决这个问题的唯一方法,因为HasExit可能随时发生变化,在实际调用流程上的方法之前,我没有多少检查。< / p>
PS2:我刚刚意识到这是一个TOCTTOU案例,除非我可以控制我打开的流程,否则无法解决这个问题,所以我要离开这里,看看有没有人知道如何做到这一点。
答案 0 :(得分:2)
简短版:你不能。
这里有一个基本的“使用时间检查时间”问题,你没有足够的控制权来解决。在您检查HasExited
属性和检查MainWindowTitle
之间的时间内,操作系统始终能够终止您正在处理的进程(无论是任意还是由于进程中的某些失败)属性。
Process
类对强制获取异常没有太大作用,但它足够了。特别是,调用Refresh()
会强制该类“忘记”它所知道的有关该过程的任何内容,以便在您再次请求它时它将重新检索该信息。这包括该过程的主窗口句柄。
Process
类使用本机窗口枚举函数来搜索已知进程ID的窗口句柄。由于该进程已退出,因此无法找到句柄,并在托管术语中返回NULL
值(IntPtr.Zero
)。在看到空返回值时,Process
类强制调用InvalidOperationException
。
唯一可靠的解决方案是始终准备好捕获异常。在检查状态和尝试做依赖它的事情之间总会有机会,状态可以改变。
虽然是学术性的,但我发现有趣的是,如果设置EnableRaisingEvents
属性,Process
类可以(并且通常是)更有效地检测已退出的进程并抛出异常。
特别是,当设置EnableRaisingEvents
属性时,Process
类注册要在发出进程句柄信号时由OS通知(通过线程池的RegisterWaitForSingleObject()
方法)。即在这种情况下,Process
类甚至不需要经历搜索主窗口句柄的工作,因为如果进程退出,它几乎立即被通知。
(当然,在一个非常小的机会窗口中仍然存在内部竞争条件,因为当Process
类检查已退出状态时,通知可能尚未到达,但是过程在Process
类枚举窗口之前可能仍然已经退出。
无论如何,这最后一点不影响基本答案;这只是我学到的一些琐事,在the Process
source code徘徊时发现有趣。 :)