如何限制.NET应用程序中的I / O操作?

我正在开发一个应用程序(.NET 4.0,C#):
1.扫描文件系统。
2.打开并读取一些文件。

该应用程序将在后台运行,应该对磁盘使用率影响较小。 如果用户正在执行常规任务并且磁盘使用率很高,则不应该打扰用户。 反之亦然,如果没有人使用磁盘,应用程序可以更快。
主要问题是由于使用API​​(mapi32.dll)来读取文件,我不知道I / O操作的实际数量和大小。 如果我要求API做某事我不知道它读取多少字节来处理我的响应。

那么问题是如何监控和管理磁盘使用情况? 包括文件系统扫描和文件读取……

检查标准性能监视器工具使用的性能计数器? 还是其他任何方式?

使用System.Diagnostics.PerformanceCounter类,附加到与要编制索引的驱动器相关的PhysicalDisk计数器。

下面是一些代码来说明,虽然它目前硬编码到“C:”驱动器。 您需要将“C:”更改为您的进程扫描的任何驱动器。 (这是粗略的示例代码,仅用于说明性能计数器的存在 – 不要将其视为提供准确信息 – 应始终仅用作指南。根据您自己的目的进行更改)

观察%空闲时间计数器,该计数器指示驱动器执行任何操作的频率。 0%空闲意味着磁盘正忙,但并不一定意味着它是平坦的并且无法传输更多数据。

%空闲时间当前磁盘队列长度相结合 ,这将告诉您驱动器是否变得如此繁忙以至于无法为所有数据请求提供服务 。 作为一般准则,任何超过0意味着驱动器可能是平坦的忙碌而超过2意味着驱动器完全饱和。 这些规则适用于SSD和HDD。

此外,您读取的任何值都是某个时间点的瞬时值。 你应该在几个结果上做一个运行平均值,例如每100ms读取一次读数并平均读取5次,然后再使用结果中的信息做出决定(即等到计数器结算然后再发出下一个IO请求)。

 internal DiskUsageMonitor(string driveName) { // Get a list of the counters and look for "C:" var perfCategory = new PerformanceCounterCategory("PhysicalDisk"); string[] instanceNames = perfCategory.GetInstanceNames(); foreach (string name in instanceNames) { if (name.IndexOf("C:") > 0) { if (string.IsNullOrEmpty(driveName)) driveName = name; } } _readBytesCounter = new PerformanceCounter("PhysicalDisk", "Disk Read Bytes/sec", driveName); _writeBytesCounter = new PerformanceCounter("PhysicalDisk", "Disk Write Bytes/sec", driveName); _diskQueueCounter = new PerformanceCounter("PhysicalDisk", "Current Disk Queue Length", driveName); _idleCounter = new PerformanceCounter("PhysicalDisk", "% Idle Time", driveName); InitTimer(); } internal event DiskUsageResultHander DiskUsageResult; private void InitTimer() { StopTimer(); _perfTimer = new Timer(_updateResolutionMillisecs); _perfTimer.Elapsed += PerfTimerElapsed; _perfTimer.Start(); } private void PerfTimerElapsed(object sender, ElapsedEventArgs e) { float diskReads = _readBytesCounter.NextValue(); float diskWrites = _writeBytesCounter.NextValue(); float diskQueue = _diskQueueCounter.NextValue(); float idlePercent = _idleCounter.NextValue(); if (idlePercent > 100) { idlePercent = 100; } if (DiskUsageResult != null) { var stats = new DiskUsageStats { DriveName = _readBytesCounter.InstanceName, DiskQueueLength = (int)diskQueue, ReadBytesPerSec = (int)diskReads, WriteBytesPerSec = (int)diskWrites, DiskUsagePercent = 100 - (int)idlePercent }; DiskUsageResult(stats); } } 

需要思考的问题:如果有其他流程遵循相同(或类似)策略会怎么样? 哪一个会在“空闲时间”运行? 其他进程是否有机会利用空闲时间?

显然,除非有一些众所周知的OS机制用于在空闲时间内公平地划分资源,否则无法正确完成。 在Windows中,这是通过调用SetPriorityClass来完成的。

这篇关于Vista中I / O优先级的文档似乎暗示IDLE_PRIORITY_CLASS不会真正降低I / O请求的优先级(尽管它会降低进程的调度优先级)。 Vista为此添加了新的PROCESS_MODE_BACKGROUND_BEGINPROCESS_MODE_BACKGROUND_END值。

在C#中,您通常可以使用Process.PriorityClass属性设置进程优先级。 但是Vista的新值不可用,因此您必须直接调用Windows API函数。 你可以这样做:

 [DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)] public static extern bool SetPriorityClass(IntPtr handle, uint priorityClass); const uint PROCESS_MODE_BACKGROUND_BEGIN = 0x00100000; static void SetBackgroundMode() { if (!SetPriorityClass(new IntPtr(-1), PROCESS_MODE_BACKGROUND_BEGIN)) { // handle error... } } 

我没有测试上面的代码。 不要忘记它只适用于Vista或更好。 您必须使用Environment.OSVersion来检查早期的操作系统并实施后备策略。

很久以前,微软研究院就此发表了一篇论文(对不起,我记不起url了)。
从我记得:

  • 该计划开始做很少的“工作项目”。
  • 他们测量了每个“工作项目”花了多长时间。
  • 运行一段时间后,他们可以计算出“工作项”在系统上没有负载的速度。
  • 从那时起,如果“工作项目”很快(例如没有其他程序员提出请求),他们会提出更多请求,否则他们会退出

基本理想是:

“如果他们让我放慢脚步,那么我必须放慢他们的速度,所以如果我放慢速度,那就少做一些工作”

请参阅此问题以及相关查询。 我建议一个简单的解决方案,只是经常查询当前磁盘和CPU使用率%,并且只有当它们处于定义的阈值时才继续当前任务。 只需确保您的工作很容易被分解为任务,并且每项任务都可以轻松高效地启动/停止。

检查屏幕保护程序是否正在运行? 很好地表明用户远离键盘