当Process.Start因"存在不足的系统资源"而失败时是什么意思?

3
我有一个C#应用程序,使用Process.Start()启动另一个可执行文件。
99%的情况下,这个调用都能正常工作。然而,当应用程序运行了相当长的时间后,Process.Start()会失败,并显示错误消息:
Insufficient system resources exist to complete the requested service

最初,我认为这可能是由于程序中存在内存泄漏 - 我已经对其进行了相当广泛的分析,但似乎没有泄漏 - 即使在此消息失败时,内存占用仍然是合理的。

在此类故障之后,如果我打印一些系统统计信息,似乎有超过600MB的可用RAM,磁盘上有足够的空间,并且CPU使用率实际上为0%。

是否还有其他我没有考虑到的系统资源?我是否遇到了.NET VM内的内存限制?

编辑2:

我在SysInternals Process Explorer中打开了应用程序,看起来我正在不断地泄漏句柄:

Handles Used: 11,950,352 (!)
GDI Handles: 26
USER Handles: 22

这里奇怪的是Win32句柄方面似乎很合理,但是我的原始句柄计数已经失控了。有什么想法可以导致这样的句柄泄漏吗?我最初认为是Process.Start(),但那应该是用户句柄,不是吗?
编辑:
这是我创建进程的一个示例:
var pInfo = new ProcessStartInfo(path, ClientStartArguments)
        {
            UseShellExecute = false,
            WorkingDirectory = workingDirectory
        };    
ClientProcess = Process.Start(pInfo);

这是一个例子,展示了我如何在程序中与进程进行交互后杀死同一进程的方法:
Process[] clientProcesses = Process.GetProcessesByName(ClientProcessName);
if (clientProcesses.Length > 0)
{
     foreach (var clientProcess in clientProcesses.Where(
              clientProcess => clientProcess.HasExited == false))
     {
        clientProcess.Kill();
     }
}

1
你正在打开什么类型的进程,控制台应用程序还是窗口应用程序?你打开了多个进程吗? - Slugart
2
你是否在实例上调用了Dispose方法?如果没有,可能会导致句柄泄漏。 - Brian Rasmussen
2
即使您可能没有泄漏内存,但您可能会泄漏窗口句柄,这些句柄需要实例化新的窗口应用程序。 - Slugart
1
Process.Start是一个静态方法,因此无法对其进行处理。即使它启动了进程的实例,也不会拥有对不同进程中窗口句柄的引用。 - Slugart
我可以使用Process.GetProcessesByName()获取进程句柄,然后调用Kill()方法来结束进程。不过,在调用Kill()方法之后,我可以尝试调用Dispose()方法。 - mweber
显示剩余2条评论
3个回答

4
问题出在保留的进程句柄上。从你后来的编辑中可以看到,你一直在引用由Process.Start()返回的Process对象。正如在Process文档中所述:

像许多Windows资源一样,进程也通过其句柄标识,该标识可能在计算机上不是唯一的。 句柄是资源标识符的通用术语。 即使进程已退出,操作系统仍会保留进程句柄,通过Process组件的Handle属性访问。 因此,您可以获取进程的管理信息,例如ExitCode(通常为成功的零或非零错误代码)和ExitTime。 句柄是一种非常有价值的资源,因此泄漏句柄比泄漏内存更具传染性。

我尤其喜欢使用“传染性”这个词。您需要处理并释放对Process的引用。
还要查看此优秀问题及其相应答案:Not enough memory or not enough handles?

2

由于Process类实现了IDisposable接口,因此在使用完毕后妥善处理它是一个良好的习惯。这样做可以防止句柄泄漏。

using (var p = new Process())
{
    p.StartInfo = new ProcessStartInfo(@"C:\windows\notepad.exe");
    p.Start();
    p.WaitForExit();
}

如果你调用 Process.Kill(),但是进程已经退出了,那么你将会得到一个 InvalidOperationException 异常。


我的应用程序需要打开这个第二个应用程序,然后让它运行,同时我对其进行操作。我没有特定的时间点想要阻塞等待退出。 - mweber
@mweber 你现在是否正在调用 Process.Start 的静态重载? - Slugart
@Slugart 有没有办法在.NET中测量窗口句柄,或者我只需要计算我所做的Process.Start()调用的数量? - mweber
1
@mweber 请查看这个问题及其优秀的答案:http://stackoverflow.com/questions/2670574/not-enough-memory-or-not-enough-handles - Slugart
@Slugart,我在阅读你提供的那个帖子后发现了更多细节——长话短说,我有一个非常严重的句柄泄漏问题,但我不认为它与Process.Start()有关——这只是因为句柄耗尽而失败的最明显的事情。 - mweber
显示剩余3条评论

1

这对像这样的小程序来说是一个常见的问题。问题在于,你使用了大量的系统资源,但却使用了很少的内存。你没有给垃圾回收器施加足够的压力,因此回收器从未运行。因此,可终结对象,即像进程和线程这样的系统句柄的包装器,永远不会被终结。

在进程退出后简单地处理进程对象将大大有助于解决问题。但可能并不能完全解决它,任何Process类使用或您自己使用的线程每个会消耗5个操作系统句柄。Thread类没有Dispose()方法。虽然应该有,但它没有,几乎不可能正确调用它。

解决方案是手动触发垃圾回收。计算启动进程的次数。例如,每隔一百次调用GC.Collect()。使用Taskmgr.exe查看句柄计数。可以使用“查看+选择列”添加它。微调GC.Collect调用,以使其不超过500。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接