Как ограничить операции ввода-вывода в приложении .NET? - PullRequest
15 голосов
/ 25 октября 2010

Я разрабатываю приложение (.NET 4.0, C #), которое:
1. Сканирует файловую систему.
2. Открывает и читает некоторые файлы.

Приложение будет работать в фоновом режиме и должно оказывать незначительное влияние на использование диска. Это не должно беспокоить пользователей, если они выполняют свои обычные задачи и использование диска является высоким. И наоборот, приложение может работать быстрее, если никто не использует диск.
Основная проблема заключается в том, что я не знаю реального количества и размера операций ввода-вывода из-за использования API (mapi32.dll) для чтения файлов. Если я прошу API сделать что-то, я не знаю, сколько байтов он читает, чтобы обработать мой ответ.

Итак, вопрос как контролировать и управлять использованием диска? Включая сканирование файловой системы и чтение файлов ...

Проверить счетчики производительности, которые используются стандартным инструментом Performance Monitor? Или какими-то другими способами?

Ответы [ 5 ]

19 голосов
/ 25 октября 2010

Используя класс System.Diagnostics.PerformanceCounter, присоедините счетчик PhysicalDisk, связанный с индексируемым диском.

Ниже приведен некоторый код для иллюстрации, хотя в настоящее время он жестко запрограммирован на диске "C:".Вы захотите изменить «C:» на тот, который сканирует ваш процесс.(Это примерный пример кода, который иллюстрирует существование счетчиков производительности - не воспринимайте его как предоставление точной информации - всегда следует использовать только в качестве ориентира. Изменения для собственных целей)

Соблюдайте % Idle Time счетчик, который показывает, как часто привод делает что-либо. 0% idle означает, что диск занят, но не обязательно означает, что он неактивен и не может передавать больше данных.

Объедините % Idle Time с Текущая длина очереди диска , и это сообщит вам, если накопитель становится настолько занятым, что не может обслуживать все запросы данных .Как правило, значение, превышающее 0, означает, что накопитель, вероятно, полностью занят, а значение, превышающее 2, означает, что накопитель полностью насыщен.Эти правила применимы как к SSD, так и к HDD.

Кроме того, любое прочитанное вами значение является мгновенным значением в определенный момент времени.Вы должны выполнить скользящее среднее по нескольким результатам, например, принять показания каждые 100 мс и в среднем 5 показаний, прежде чем использовать информацию из результата для принятия решения (т. Е. Дождаться, пока счетчики не сработают, прежде чем делать ваш следующий запрос ввода-вывода).

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);
    }
}
3 голосов
/ 25 октября 2010

Надо подумать: что, если есть другие процессы, которые следуют той же (или похожей) стратегии? Какой из них будет работать во время «простоя»? Получат ли другие процессы возможность вообще использовать время простоя?

Очевидно, что это нельзя сделать правильно, если не существует какого-либо хорошо известного механизма ОС для справедливого разделения ресурсов во время простоя. В Windows это делается путем вызова SetPriorityClass .

Этот документ о приоритезации ввода / вывода в Vista , по-видимому, подразумевает, что IDLE_PRIORITY_CLASS действительно не снизит приоритет запросов ввода / вывода (хотя это уменьшит приоритет планирования для процесса). Vista добавила для этого новые значения PROCESS_MODE_BACKGROUND_BEGIN и PROCESS_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, чтобы проверить более ранние операционные системы и реализовать запасную стратегию.

2 голосов
/ 25 октября 2010

Давным-давно Microsoft Research опубликовала статью на эту тему (извините, я не помню URL).
Из того, что я помню:

  • Программа начала делать очень мало "рабочие элементы ".
  • Они измерили, сколько времени потребовалось для каждого их" рабочего элемента ".
  • Пройдя немного времени, они могли определить, насколько быстро был" рабочий элемент "сбез нагрузки в системе.
  • С тех пор, если «рабочий элемент» был быстрым (например, никакие другие программисты не отправляли запросы), они делали больше запросов, в противном случае они отступали

Основной идеал:

«если они замедляют меня, то я, должно быть, замедляю их, поэтому выполняйте меньше работы, если меня замедляют»

1 голос
/ 25 октября 2010

См. этот вопрос и это также для связанных запросов.Я бы предложил простое решение, просто запрашивая текущее использование диска и ЦП%, и продолжайте выполнять текущую задачу только тогда, когда они находятся ниже определенного порога.Просто убедитесь, что ваша работа легко разбита на задачи, и каждая задача может быть легко и эффективно запущена / остановлена.

0 голосов
/ 25 октября 2010

Проверьте, работает ли заставка?Хороший признак того, что пользователь находится далеко от клавиатуры

...