Как ограничить операции ввода-вывода в приложении .NET?

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

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

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

Спасибо

Ответы

Ответ 1

Используя класс 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);
    }
}

Ответ 2

Что-то подумать: что, если есть другие процессы, которые следуют одной и той же (или аналогичной) стратегии? Какой из них будет работать во время "простоя"? Удастся ли другим процессам использовать время простоя?

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

Этот документ о приоритезации ввода-вывода в Vista, по-видимому, подразумевает, что IDLE_PRIORITY_CLASS не будет действительно уменьшать приоритет запросов ввода-вывода ( хотя это уменьшит приоритет планирования для процесса). Vista добавила для этого значения PROCESS_MODE_BACKGROUND_BEGIN и PROCESS_MODE_BACKGROUND_END.

В С# вы обычно можете установить приоритет процесса с помощью свойства 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 для проверки более ранних операционных систем и реализации стратегии снижения.

Ответ 3

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

Ответ 4

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

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

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

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

Ответ 5

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