2010-10-25 8 views
14

Estoy desarrollando una aplicación (.NET 4.0, C#) que:
1. Escanea el sistema de archivos.
2. Abre y lee algunos archivos.¿Cómo limitar las operaciones de E/S en la aplicación .NET?

La aplicación funcionará en segundo plano y debería tener un bajo impacto en el uso del disco. No debería molestar a los usuarios si están realizando sus tareas habituales y el uso del disco es alto. Y viceversa, la aplicación puede ir más rápido si nadie está usando el disco.
El problema principal es que no sé la cantidad real y el tamaño de las operaciones de E/S debido al uso de API (mapi32.dll) para leer archivos. Si le pido a API que haga algo, no sé cuántos bytes lee para manejar mi respuesta.

¿Entonces la pregunta es cómo controlar y administrar el uso del disco? Incluyendo el análisis del sistema de archivos y la lectura de archivos ...
¿Compruebe los contadores de rendimiento que utiliza la herramienta estándar Monitor de rendimiento? O de alguna otra manera?

Gracias

+0

¿El servicio tendrá privilegios administrativos o se ejecutará como una cuenta de usuario normal? (Los contadores de rendimiento para el disco físico requieren privilegios de administrador). Dependiendo de la respuesta, puede haber otra manera. –

+0

Sí, tendrá privilegios de administrador. – Anatoly

Respuesta

19

Con la clase System.Diagnostics.PerformanceCounter, conéctela al contador de PhysicalDisk relacionado con la unidad que está indexando.

A continuación se muestra un código para ilustrar, aunque actualmente está codificado de forma rígida para la unidad "C:". Deseará cambiar "C:" a la unidad que esté escaneando su proceso. (Este es un código de muestra aproximado solo para ilustrar la existencia de contadores de rendimiento; no lo tome como una información precisa) siempre debe usarse como guía. Cambie para su propio propósito)

Observe el % Tiempo de inactividad Contador que indica con qué frecuencia el disco está haciendo algo. 0% inactivo significa que el disco está ocupado, pero no significa necesariamente que sea plano y no pueda transferir más datos.

combinar los % Tiempo de inactividad con Current Disk Longitud de la cola y esto le dirá si la unidad está recibiendo tan ocupado que no puede dar servicio a todas las solicitudes de datos. Como regla general, cualquier valor superior a 0 significa que el disco probablemente esté ocupado y cualquier valor superior a 2 significa que el disco está completamente saturado. Estas reglas se aplican a SSD y HDD bastante bien.

Además, cualquier valor que lea es un valor instantáneo en un punto en el tiempo. Deberías hacer un promedio continuo de unos pocos resultados, p. realice una lectura cada 100 ms y promedie 5 lecturas antes de utilizar la información del resultado para tomar una decisión (es decir, esperar hasta que los contadores se resuelvan antes de realizar su próxima solicitud de autorización de ingreso).

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); 
    } 
} 
+0

¡Gran respuesta! Quise decir los mismos contadores, pero perdí clases relacionadas del espacio de nombres de System.Diagnostics. Ejemplo muy útil. Ahora veo la solución mejor. Este enfoque cumple mis expectativas. ¡¡¡Muchas gracias!!! – Anatoly

0

Compruebe si el protector de pantalla está en ejecución? Buena indicación de que el usuario está fuera del teclado

+0

La aplicación se implementará como Servicio de Win – Anatoly

+0

Con esto, múltiples usuarios pueden iniciar sesión al mismo tiempo. Y en cuanto a mí, generalmente apago el protector de pantalla;) Así que no estoy seguro de que sea una buena idea. – Anatoly

+0

Por cierto, cualquier otro servicio puede usar el disco aunque nadie haya iniciado sesión. No puedo afectar su rendimiento. – Anatoly

1

Ver this question y this also para consultas relacionadas. Yo sugeriría una solución simple simplemente preguntando por el disco actual & Uso de CPU% de vez en cuando, y solo continuar con la tarea actual cuando están por debajo de un umbral definido. Solo asegúrese de que su trabajo se divida fácilmente en tareas y que cada tarea se pueda iniciar 0 detener fácilmente.

+0

Gracias por el segundo enlace. Hay contadores que quise decir, pero que olvidé envoltorios administrados. – Anatoly

1

Hace un largo plazo de Microsoft Research publicó un artículo sobre esto (lo siento, no puedo recordar la URL).
de lo que recuerdo:

  • El programa se ubicó haciendo muy pocos "elementos de trabajo".
  • Midieron cuánto tiempo les tomó a cada uno de sus "elementos de trabajo".
  • Después de correr por un tiempo, pudieron calcular qué tan rápido era un "elemento de trabajo" sin carga en el sistema.
  • A partir de entonces, si el "elemento de trabajo" era rápido (por ejemplo, no hay otros programadores hacer peticiones), que hicieron más solicitudes, de lo contrario retraída

El ideal básico es:

“si me están desacelerando, entonces yo debo ser hacerlos más lentos, por lo menos trabajo si estoy siendo ralentizado”

3

Algo para reflexionar: ¿y si hay otros proces ses que siguen la misma estrategia (o una estrategia similar)? ¿Cuál se ejecutará durante el "tiempo de inactividad"? ¿Tendrían los otros procesos la oportunidad de utilizar el tiempo de inactividad?

Obviamente, esto no se puede hacer correctamente a menos que exista algún mecanismo bien conocido del sistema operativo para dividir los recursos equitativamente durante el tiempo de inactividad. En Windows, esto se hace llamando al SetPriorityClass.

Este document about I/O prioritization in Vista parece implicar que IDLE_PRIORITY_CLASS realmente no reducirá la prioridad de las solicitudes de E/S (aunque reducirá la prioridad de programación para el proceso). Vista agregó nuevos valores PROCESS_MODE_BACKGROUND_BEGIN y PROCESS_MODE_BACKGROUND_END para eso.

En C#, normalmente puede establecer la prioridad del proceso con la propiedad Process.PriorityClass. Sin embargo, los nuevos valores para Vista no están disponibles, por lo que deberá llamar directamente a la función API de Windows. Puede hacerlo así:

[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... 
    } 
} 

No he probado el código anterior. No olvide que solo puede funcionar en Vista o mejor. Tendrá que usar Environment.OSVersion para verificar sistemas operativos anteriores e implementar una estrategia de repliegue.

+2

'SetPriorityClass' espera un identificador,' Process.GetCurrentProcess(). Id' es un ID de proceso, por lo que no funcionará. Afortunadamente, simplemente puede pasar 'new IntPtr (-1)' a 'SetPriorityClass', ya que es un pseudohandle para el proceso actual. Con estos cambios, funciona. También debe verificar el valor de retorno de 'SetPriorityClass' para ver si realmente funcionó. – CodesInChaos

Cuestiones relacionadas