2010-10-25 4 views
14

Sto sviluppando un'applicazione (.NET 4.0, C#) che:
1. Scansione del file system.
2. Apre e legge alcuni file.Come limitare le operazioni di I/O nell'applicazione .NET?

L'applicazione funzionerà in background e dovrebbe avere un basso impatto sull'utilizzo del disco. Non dovrebbe disturbare gli utenti se stanno facendo le loro solite attività e l'utilizzo del disco è elevato. E viceversa, l'app può andare più veloce se nessuno usa il disco.
Il problema principale è che non conosco la quantità e le dimensioni reali delle operazioni di I/O a causa dell'utilizzo dell'API (mapi32.dll) per leggere i file. Se chiedo all'API di fare qualcosa, non so quanti byte legge per gestire la mia risposta.

Quindi la domanda è come monitorare e gestire l'utilizzo del disco? Inclusione della scansione del file system e lettura dei file ...
Controllare i contatori delle prestazioni utilizzati dallo strumento Performance Monitor standard? O altri modi?

Grazie

+0

Il servizio dispone di privilegi amministrativi o può essere eseguito come un normale account utente? (I contatori di prestazioni per dischi fisici richiedono privilegi di amministratore). A seconda della risposta, potrebbe esserci un altro modo. –

+0

Sì, avrà i privilegi di amministratore. – Anatoly

risposta

19

Utilizzando la classe System.Diagnostics.PerformanceCounter, collegarsi al contatore PhysicalDisk relativo all'unità che si sta indicizzando.

Di seguito è riportato un codice per illustrare, sebbene sia attualmente codificato nel disco "C:". Dovrai cambiare "C:" a seconda del tuo processo di scansione. (Questo è un codice di esempio approssimativo solo per illustrare l'esistenza di contatori delle prestazioni - non utilizzarlo come fornire informazioni accurate - deve sempre essere utilizzato solo come guida. Modifica per i propri scopi)

Rispettare il tempo di inattività % Contatore che indica quanto spesso l'unità sta facendo qualcosa. 0% inattivo significa che il disco è occupato, ma non significa necessariamente che è flat-out e non può trasferire più dati.

combinare il tempo di inattività % con corrente coda del disco Lunghezza e questo vi dirà se l'unità è sempre così occupato che non può servire tutte le richieste di dati. Come regola generale, qualsiasi valore superiore a 0 indica che l'unità è probabilmente esaurita e che qualcosa oltre 2 significa che l'unità è completamente saturata. Queste regole si applicano sia a SSD che a HDD abbastanza bene.

Inoltre, qualsiasi valore letto è un valore istantaneo in un punto nel tempo. Dovresti eseguire una media parziale su alcuni risultati, ad es. effettuare una lettura ogni 100 ms e una media di 5 letture prima di utilizzare le informazioni dal risultato per prendere una decisione (ad esempio, attendere fino a quando i contatori si stabiliscono prima di effettuare la successiva richiesta di I/O).

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

Ottima risposta! Intendevo gli stessi contatori ma mancavano le classi correlate dallo spazio dei nomi System.Diagnostics. Esempio molto utile Ora vedo meglio la soluzione. Questo approccio soddisfa le mie aspettative. Grazie molto!!! – Anatoly

0

Verificare se lo screensaver è in esecuzione? Buona indicazione che l'utente è lontano dalla tastiera

+0

L'app verrà distribuita come servizio Win – Anatoly

+0

In questo modo, è possibile accedere contemporaneamente a più utenti. E per quanto mi riguarda, di solito spengo lo screensaver;) Quindi non sono sicuro che sia una buona idea. – Anatoly

+0

Btw, tutti gli altri servizi possono usare il disco anche se nessuno è loggato. Non posso influenzare le loro prestazioni. – Anatoly

1

Vedere this question e this also per le query correlate. Vorrei suggerire per una soluzione semplice solo l'interrogazione per il disco corrente &% di utilizzo della CPU ogni tanto, e solo continuare con l'attività corrente quando sono sotto una soglia definita. Assicurati che il tuo lavoro sia facilmente suddiviso in attività e che ogni attività possa essere facilmente avviata/interrotta & in modo efficiente.

+0

Grazie per il secondo link. Ci sono segnalini che intendevo ma involucri gestiti mancati. – Anatoly

1

A lungo termine fa Microsoft Research ha pubblicato un documento su questo (scusate non ricordo l'url).
Da quello che mi ricordo:

  • Il programma è iniziato fuori facendo molto pochi "elementi di lavoro".
  • Hanno misurato il tempo necessario per ciascuno dei "lavori".
  • Dopo aver eseguito per un po ', potevano calcolare la velocità con cui un "oggetto di lavoro" era senza carico sul sistema.
  • Da allora in poi, se il "elemento di lavoro" sono stati veloci (ad esempio non altri programmatori fare richieste), hanno fatto più richieste, altrimenti arretrata

L'ideale di base è:

“se mi stanno rallentando, poi ho devo essere li rallentare, in modo da fare meno lavoro se mi viene rallentato”

3

Qualcosa da riflettere: cosa succede se ci sono altri proces ses che seguono la stessa strategia (o una strategia simile)? Quale sarebbe eseguito durante il "tempo di inattività"? Gli altri processi avrebbero la possibilità di utilizzare il tempo di inattività?

Ovviamente questo non può essere eseguito correttamente a meno che non vi sia un meccanismo OS ben noto per dividere equamente le risorse durante il tempo di inattività. In Windows, questo viene fatto chiamando SetPriorityClass.

Questo document about I/O prioritization in Vista sembra implicare che IDLE_PRIORITY_CLASS non ridurrà realmente la priorità delle richieste di I/O (anche se ridurrà la priorità di pianificazione per il processo). Vista ha aggiunto nuovi valori PROCESS_MODE_BACKGROUND_BEGIN e PROCESS_MODE_BACKGROUND_END per quello.

In C#, è possibile impostare normalmente la priorità del processo con la proprietà Process.PriorityClass. Tuttavia, i nuovi valori di Vista non sono disponibili, quindi dovrai chiamare direttamente la funzione API di Windows. Puoi fare così:

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

Non ho provato il codice sopra. Non dimenticare che può funzionare solo su Vista o meglio. Dovrai utilizzare Environment.OSVersion per verificare la presenza di sistemi operativi precedenti e implementare una strategia di fallback.

+2

'SetPriorityClass' si aspetta un handle,' Process.GetCurrentProcess(). Id' è un ID di processo, quindi non funzionerà. Fortunatamente puoi semplicemente passare 'new IntPtr (-1)' a 'SetPriorityClass', poiché è una pseudo-mano per il processo corrente. Con questi cambiamenti funziona. Inoltre dovresti controllare il valore di ritorno di "SetPriorityClass" per vedere se ha funzionato. – CodesInChaos