【问题标题】:How to limit I/O operations in .NET application?如何限制 .NET 应用程序中的 I/O 操作?
【发布时间】:2015-11-15 13:05:49
【问题描述】:

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

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

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

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

【问题讨论】:

  • 服务是否具有管理权限或作为普通用户帐户运行? (物理磁盘的性能计数器需要管理员权限)。根据答案,可能还有另一种方式。
  • 是的,它将拥有管理员权限。

标签: c# .net io limit


【解决方案1】:

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

【讨论】:

  • 应用将部署为 Win Service
  • 这样,多个用户可以同时登录。至于我,我通常会关掉屏幕保护程序;)所以不确定这是个好主意。
  • 顺便说一句,任何其他服务都可以使用磁盘,即使没有人登录。我不能影响它们的性能。
【解决方案2】:

相关查询请参见this questionthis also。我建议一个简单的解决方案,只是每隔一段时间查询当前的磁盘和 CPU 使用率,并且只有在它们低于定义的阈值时才继续执行当前任务。只需确保您的工作很容易分解为任务,并且每个任务都可以轻松高效地启动/停止。

【讨论】:

  • 感谢第二个链接。我的意思是有计数器,但错过了托管包装器。
【解决方案3】:

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

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

观察 % Idle Time 计数器,该计数器指示驱动器执行任何操作的频率。 0% idle 表示磁盘繁忙,但并不一定表示磁盘已用尽,无法传输更多数据。

% Idle TimeCurrent Disk Queue Length 结合起来,这将告诉您驱动器是否变得忙到无法满足所有请求数据。作为一般准则,任何超过 0 的值都意味着驱动器可能完全繁忙,而任何超过 2 的值都意味着驱动器完全饱和。这些规则相当适用于 SSD 和 HDD。

此外,您读取的任何值都是某个时间点的瞬时值。您应该对一些结果进行运行平均,例如在使用结果中的信息做出决定之前每 100 毫秒读取一次读数并平均 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);
    }
}

【讨论】:

  • 很好的答案!我的意思是相同的计数器,但错过了 System.Diagnostics 命名空间中的相关类。非常有用的例子。现在我更好地看到了解决方案。这种方法符合我的期望。非常感谢!!!
【解决方案4】:

很久以前,微软研究院发表了一篇关于此的论文(抱歉,我不记得网址了)。
据我回忆:

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

基本理想是:

“如果他们让我慢下来,那么我 一定是让他们慢下来,所以少做 如果我的速度慢了就工作”

【讨论】:

    【解决方案5】:

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

    除非有一些众所周知的操作系统机制可以在空闲时间公平分配资源,否则这显然无法正确完成。在 Windows 中,这是通过调用 SetPriorityClass 来完成的。

    这个document about I/O prioritization in Vista 似乎暗示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 来检查较早的操作系统并实施后备策略。

    【讨论】:

    • SetPriorityClass 需要一个句柄,Process.GetCurrentProcess().Id 是一个进程 ID,所以这不起作用。幸运的是,您可以简单地将new IntPtr(-1) 传递给SetPriorityClass,因为这是当前进程的伪句柄。通过这些更改,它可以工作。您还应该检查SetPriorityClass 的返回值,看看它是否真的有效。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-25
    • 1970-01-01
    • 2010-09-06
    • 2011-11-14
    相关资源
    最近更新 更多