更新:在发生此事件的时间查看事件日志后,我收到消息:“服务器无法从系统非页面缓冲池分配,因为池为空“。在整个日志中不断重复,直到重新启动。
我正在编写一个将调试信息写入文件的类,到目前为止,该类工作正常,但我现在开始对我的应用程序进行压力测试(运行速度比正常速度快1000倍)导致发生异常错误。
我看到的问题是,经过很长一段时间(4小时+)后,我的应用程序崩溃了,似乎用它取出了Windows;我无法再打开Windows资源管理器或任何其他应用程序。系统重启似乎解决了这个问题,但是当我写文件时我写的是空白的。
这让我觉得这个问题可能与打开文件句柄有关;也许Windows以某种方式达到了打开文件句柄的限制?
所以,这里有相关的问题;这是将数据写入文件的主要功能。如您所见,FileStream和BinaryWriter对象是在每次调用此函数时创建的,包含在using语句中以确保它们被正确关闭/处置。
/// <summary>
/// This is called after changing any
/// stats data, or on initial startup.
/// It saves the current stats to file.
/// </summary>
public void UpdateStatsData()
{
lock (this.lockObject)
{
using (FileStream fileStream = new FileStream(Constants.StatsFile, FileMode.Create, FileAccess.Write, FileShare.None, 128, FileOptions.WriteThrough))
{
using (BinaryWriter binWriter = new BinaryWriter(fileStream))
{
binWriter.Write(this.serverStats.APM);
binWriter.Write(this.serverStats.AverageJackpotWin);
binWriter.Write(this.serverStats.AverageWinnings);
binWriter.Write(this.serverStats.NumberOfGamesPlayed);
binWriter.Write(this.serverStats.NumberOfJackpots);
binWriter.Write(this.serverStats.RunningPercentage);
binWriter.Write(this.serverStats.SiteID);
binWriter.Write(this.serverStats.TotalJackpotsValue);
binWriter.Write(this.serverStats.TotalStaked);
binWriter.Write(this.serverStats.TotalWinnings);
}
}
}
}
当快速调用此函数时,是否可能导致文件句柄缓慢增加并最终超出Windows的最大值?
一种可能的解决方案是使FileStream和BinaryWriter对象成为类的私有成员变量,在构造函数中创建它们,然后在每次调用时覆盖数据。
/// <summary>
/// This should be called after changing any
/// stats data, or on initial startup.
/// It saves the current stats to a serialized file.
/// </summary>
public void UpdateStatsData()
{
lock (this.lockObject)
{
// Seek to the beginning of the file.
this.binWriter.BaseStream.Seek(0, SeekOrigin.Begin);
// Write the stats data over the existing data.
this.binWriter.Write(this.serverStats.APM);
this.binWriter.Write(this.serverStats.AverageJackpotWin);
this.binWriter.Write(this.serverStats.AverageWinnings);
this.binWriter.Write(this.serverStats.NumberOfGamesPlayed);
this.binWriter.Write(this.serverStats.NumberOfJackpots);
this.binWriter.Write(this.serverStats.RunningPercentage);
this.binWriter.Write(this.serverStats.SiteID);
this.binWriter.Write(this.serverStats.TotalJackpotsValue);
this.binWriter.Write(this.serverStats.TotalStaked);
this.binWriter.Write(this.serverStats.TotalWinnings);
}
}
然而,虽然它可能更快并且仅意味着使用一个FileStream,但如何在应用程序关闭时确保FileStream和BinaryWriter正确关闭/处置?
答案 0 :(得分:2)
FileStream
构造函数的参数组合看起来很可疑(假设所有线程都记录到同一个文件(Constants.StatsFile
):
FileMode.Create
=始终创建文件。如果存在则覆盖。您要删除此方法中每个条目的所有先前日志(可能会尝试OpenOrCreate
或Append
)FileOptions.WriteThrough
=没有缓存 - 强制磁盘旋转并强制线程等待磁盘 - 慢我的猜测:你比这更快地调用这种方法。每个调用都会在lock
语句上进行备份,等待上一次调用以删除文件,写入文件并将其完全刷新到磁盘。过了一会儿,你的内存已经用完了。
假设您每次尝试使用此组合时都不打算删除日志文件,看看事情是否变得更好并且至少可以摆脱WriteThrough
,因为这会使此方法更快:
using (FileStream fileStream = new FileStream(Constants.StatsFile, FileMode.Append,
FileAccess.Write, FileShare.None, 128, FileOptions.SequentialScan))
答案 1 :(得分:2)
耗尽非分页池内存是Windows中一个非常严重的错误。之后没有什么好事发生,司机将无法完成他们的工作,需要重新启动才能从中恢复。
当然,用户模式程序(管理员程序)导致这种情况发生是不正常的。 Windows通过为进程提供有限的可用系统资源配置来保护自己免受此攻击。其中有很多,如果一个程序泄漏句柄,那么很明显会出现10,000个句柄的限制。
来自非分页池的内存由驱动程序专门分配。它们需要那种宝贵的内存,因为它们在设备中断时使用内存。无法从分页文件映射内存的关键时刻。池很小,需要因为它永久占用RAM。这取决于您的机器具有的RAM量,对于具有1 GB RAM的机器,通常为256 MB。您可以在TaskMgr.exe,Performance选项卡中查看其当前大小。我现在给它一个体面的解决方法,它目前显示61 MB。
显然,您的程序正在使您的计算机上的驱动程序消耗过多的非页面池内存。或者它是泄漏,可能是由你给它的重度锻炼引起的。 Windows无法阻止这种情况,配额与进程相关联,而不是与驱动程序相关联。你必须找到行为不端的司机。它将与文件系统或磁盘相关联。一个非常常见的导致这样的麻烦的是,你现在可能已经猜到了你的病毒扫描程序。
答案 2 :(得分:1)
这些代码大部分对我来说都很好 - 你应该没有问题像你一样重新创建FileStreams。
唯一突然出现的是你的lockObject不是静态的。这可能是一个大问题 - 类的多个实例将导致阻塞而不是,这意味着您可能会遇到由多个线程同时运行相同代码导致的某些奇怪情况。谁知道,在加载时你可以同时创建数千个打开的文件句柄。
答案 3 :(得分:1)
在句柄关闭方面,我认为第一个没有错。我做第二个;特别是你问的问题。您可以让您的课程一次性使用,然后在“受控”关闭期间理想地关闭它,同时取决于文件对象的终结者在特殊关闭期间处理问题,但我不确定您是否正在修复问题。
打开文件句柄的哪些测量结果证实您怀疑这是问题所在?当你确实打开大量文件时怀疑打开文件句柄是合理的,但是“修复”它是愚蠢的,除非A)检查代码显示它显然会有这个问题(不是这里的情况)或B)你已经表明这样的文件句柄确实太高了。
应用程序在崩溃时是否在事件查看器中出现异常?