L'I/O del disco asincrono viene visualizzato come sincrono in Windows

Questo articolo consente di risolvere il problema in cui il comportamento predefinito per l'I/O è sincrono, ma viene visualizzato come asincrono.

              Versione originale del prodotto: Windows
Numero KB originale: 156932

Riepilogo

L'I/O dei file in Microsoft Windows può essere sincrono o asincrono. Il comportamento predefinito per I/O è sincrono, in cui viene chiamata una funzione di I/O e restituisce al termine dell'I/O. L'I/O asincrono consente a una funzione di I/O di restituire immediatamente l'esecuzione al chiamante, ma si presuppone che l'I/O venga completato solo in un secondo momento. Il sistema operativo invia una notifica al chiamante al termine dell'I/O. Il chiamante può invece determinare lo stato dell'operazione di I/O in sospeso usando i servizi del sistema operativo.

Il vantaggio dell'I/O asincrono è che il chiamante ha il tempo di eseguire altre operazioni o di inviare più richieste durante il completamento dell'operazione di I/O. Il termine I/O sovrapposto viene spesso usato per I/O asincrono e I/O non sovrapposto per I/O sincrono. Questo articolo usa i termini asincrono e sincrono per le operazioni di I/O. Questo articolo presuppone che il lettore abbia familiarità con le funzioni di I/O file, ad CreateFileesempio , ReadFile, WriteFile.

Spesso, le operazioni di I/O asincrone si comportano esattamente come operazioni di I/O sincrone. Alcune condizioni illustrate in questo articolo nelle sezioni successive, che consentono di completare le operazioni di I/O in modo sincrono. Il chiamante non ha tempo per il lavoro in background perché le funzioni di I/O non restituiscono fino al completamento dell'I/O.

Diverse funzioni sono correlate all'I/O sincrono e asincrono. Questo articolo usa ReadFile e WriteFile come esempi. Le alternative valide sarebbero ReadFileEx e WriteFileEx. Anche se questo articolo illustra in modo specifico solo l'I/O su disco, molti dei principi possono essere applicati ad altri tipi di I/O, ad esempio I/O seriale o I/O di rete.

Configurare un I/O asincrono

Il FILE_FLAG_OVERLAPPED flag deve essere specificato in CreateFile quando il file viene aperto. Questo flag consente di eseguire operazioni di I/O sul file in modo asincrono. Ecco un esempio:

HANDLE hFile;

hFile = CreateFile(szFileName,
                      GENERIC_READ,
                      0,
                      NULL,
                      OPEN_EXISTING,
                      FILE_FLAG_NORMAL | FILE_FLAG_OVERLAPPED,
                      NULL);

if (hFile == INVALID_HANDLE_VALUE)
      ErrorOpeningFile();

Prestare attenzione quando si esegue il codice per l'I/O asincrono perché il sistema si riserva il diritto di rendere sincrona un'operazione, se necessario. È quindi consigliabile scrivere il programma per gestire correttamente un'operazione di I/O che può essere completata in modo sincrono o asincrono. Il codice di esempio illustra questa considerazione.

Un programma può eseguire molte operazioni durante l'attesa del completamento delle operazioni asincrone, ad esempio l'accodamento di operazioni aggiuntive o l'esecuzione di operazioni in background. Ad esempio, il codice seguente gestisce correttamente il completamento sovrapposto e non sovrapposto di un'operazione di lettura. Non fa altro che attendere il completamento dell'I/O in sospeso:

if (!ReadFile(hFile,
               pDataBuf,
               dwSizeOfBuffer,
               &NumberOfBytesRead,
               &osReadOperation )
{
   if (GetLastError() != ERROR_IO_PENDING)
   {
      // Some other error occurred while reading the file.
      ErrorReadingFile();
      ExitProcess(0);
   }
   else
      // Operation has been queued and
      // will complete in the future.
      fOverlapped = TRUE;
}
else
   // Operation has completed immediately.
   fOverlapped = FALSE;

if (fOverlapped)
{
   // Wait for the operation to complete before continuing.
   // You could do some background work if you wanted to.
   if (GetOverlappedResult( hFile,
                           &osReadOperation,
                           &NumberOfBytesTransferred,
                           TRUE))
      ReadHasCompleted(NumberOfBytesTransferred);
   else
      // Operation has completed, but it failed.
      ErrorReadingFile();
}
else
   ReadHasCompleted(NumberOfBytesRead);

Nota

&NumberOfBytesRead passato in ReadFile è diverso da &NumberOfBytesTransferred passato in GetOverlappedResult. Se un'operazione è stata resa asincrona, GetOverlappedResult viene usata per determinare il numero effettivo di byte trasferiti nell'operazione dopo il completamento. L'oggetto &NumberOfBytesRead passato in ReadFile è privo di significato.

D'altra parte, se un'operazione viene completata immediatamente, il &NumberOfBytesRead passaggio a ReadFile è valido per il numero di byte letti. In questo caso, ignorare la OVERLAPPED struttura passata in ReadFile. Non usarla con GetOverlappedResult o WaitForSingleObject.

Un'altra avvertenza con l'operazione asincrona è che non è necessario usare una OVERLAPPED struttura fino al completamento dell'operazione in sospeso. In altre parole, se si dispone di tre operazioni di I/O in sospeso, è necessario usare tre OVERLAPPED strutture. Se si riutilizza una OVERLAPPED struttura, si riceveranno risultati imprevedibili nelle operazioni di I/O e si potrebbe riscontrare un danneggiamento dei dati. Inoltre, è necessario inizializzarla correttamente in modo che nessun dato residuo influisca sulla nuova operazione prima di poter usare una OVERLAPPED struttura per la prima volta o prima di riutilizzarla dopo il completamento di un'operazione precedente.

Lo stesso tipo di restrizione si applica al buffer di dati usato in un'operazione. Un buffer di dati non deve essere letto o scritto fino al completamento dell'operazione di I/O corrispondente; la lettura o la scrittura del buffer possono causare errori e dati danneggiati.

L'I/O asincrono sembra ancora sincrono

Se sono state seguite le istruzioni riportate in precedenza in questo articolo, tuttavia, tutte le operazioni di I/O vengono comunque in genere completate in modo sincrono nell'ordine emesso e nessuna delle ReadFile operazioni restituisce FALSE con GetLastError() la restituzione ERROR_IO_PENDINGdi , il che significa che non si ha tempo per alcun lavoro in background. Perché si verifica questo problema?

Esistono vari motivi per cui le operazioni di I/O vengono completate in modo sincrono anche se è stato codificato per l'operazione asincrona.

Compressione

Un ostacolo all'operazione asincrona è la compressione NTFS (New Technology File System). Il driver del file system non accederà ai file compressi in modo asincrono; tutte le operazioni vengono invece rese sincrone. Questo ostacolo non si applica ai file compressi con utilità simili a COMPRESS o PKZIP.

Crittografia NTFS

Analogamente alla compressione, la crittografia dei file fa sì che il driver di sistema converta l'I/O asincrono in sincrono. Se i file vengono decrittografati, le richieste di I/O saranno asincrone.

Estendere un file

Un altro motivo per cui le operazioni di I/O vengono completate in modo sincrono sono le operazioni stesse. In Windows, qualsiasi operazione di scrittura in un file che ne estende la lunghezza sarà sincrona.

Nota

Le applicazioni possono rendere asincrona l'operazione di scrittura menzionata in precedenza modificando la lunghezza dei dati valida del file usando la SetFileValidData funzione e quindi emettendo un WriteFileoggetto .

Usando SetFileValidData (disponibile in Windows XP e versioni successive), le applicazioni possono estendere in modo efficiente i file senza incorrere in una riduzione delle prestazioni per il loro riempimento zero.

Poiché il file system NTFS non riempie i dati fino alla lunghezza di dati valida (VDL) definita da SetFileValidData, questa funzione ha implicazioni sulla sicurezza in cui al file possono essere assegnati cluster precedentemente occupati da altri file. Pertanto, SetFileValidData richiede che il chiamante abbia il nuovo SeManageVolumePrivilege abilitato (per impostazione predefinita, questo viene assegnato solo agli amministratori). Microsoft consiglia ai fornitori di software indipendenti (ISV) di considerare attentamente le implicazioni dell'uso di tale funzione.

Cache

La maggior parte dei driver di I/O (disco, comunicazioni e altri) ha un codice caso speciale in cui, se una richiesta di I/O può essere completata immediatamente, l'operazione verrà completata e la ReadFile funzione o WriteFile restituirà TRUE. In tutti i modi, questi tipi di operazioni sembrano essere sincroni. Per un dispositivo disco, in genere, una richiesta di I/O può essere completata immediatamente quando i dati vengono memorizzati nella cache in memoria.

I dati non sono nella cache

Tuttavia, lo schema della cache può funzionare in base all'utente se i dati non sono nella cache. La cache di Windows viene implementata internamente usando i mapping dei file. Gestione memoria in Windows non fornisce un meccanismo di errore di pagina asincrono per gestire i mapping dei file usati dalla gestione cache. Il gestore della cache può verificare se la pagina richiesta è in memoria, quindi se si esegue una lettura memorizzata nella cache asincrona e le pagine non sono in memoria, il driver del file system presuppone che il thread non venga bloccato e che la richiesta venga gestita da un pool limitato di thread di lavoro. Il controllo viene restituito al programma dopo la ReadFile chiamata con la lettura ancora in sospeso.

Questa operazione funziona correttamente per un numero ridotto di richieste, ma poiché il pool di thread di lavoro è limitato (attualmente tre in un sistema da 16 MB), saranno ancora presenti solo poche richieste in coda al driver del disco in un determinato momento. Se si eseguono numerose operazioni di I/O per i dati non presenti nella cache, gestione cache e gestione memoria diventano saturi e le richieste vengono rese sincrone.

Il comportamento della gestione cache può anche essere influenzato in base all'accesso sequenziale o casuale a un file. I vantaggi della cache sono più visibili quando si accede ai file in sequenza. Il FILE_FLAG_SEQUENTIAL_SCAN flag nella CreateFile chiamata ottimizza la cache per questo tipo di accesso. Tuttavia, se si accede ai file in modo casuale, usare il FILE_FLAG_RANDOM_ACCESS flag in CreateFile per indicare alla gestione cache di ottimizzarne il comportamento per l'accesso casuale.

Non usare la cache

Il FILE_FLAG_NO_BUFFERING flag ha l'effetto maggiore sul comportamento del file system per l'operazione asincrona. È il modo migliore per garantire che le richieste di I/O siano asincrone. Indica al file system di non usare alcun meccanismo di cache.

Nota

Esistono alcune restrizioni all'uso di questo flag che hanno a che fare con l'allineamento del buffer di dati e le dimensioni del settore del dispositivo. Per altre informazioni, vedere le informazioni di riferimento sulla funzione nella documentazione relativa alla funzione CreateFile sull'uso corretto di questo flag.

Risultati dei test del mondo reale

Di seguito sono riportati alcuni risultati del test del codice di esempio. La grandezza dei numeri non è importante qui e varia da computer a computer, ma la relazione dei numeri confrontati tra loro illumina l'effetto generale dei flag sulle prestazioni.

È possibile prevedere risultati simili a uno dei seguenti:

  • Test 1

    Asynchronous, unbuffered I/O:  asynchio /f*.dat /n
    Operations completed out of the order in which they were requested.
       500 requests queued in 0.224264 second.
       500 requests completed in 4.982481 seconds.
    

    Questo test dimostra che il programma indicato in precedenza ha emesso 500 richieste di I/O rapidamente e ha avuto molto tempo per eseguire altre operazioni o inviare più richieste.

  • Test 2

    Synchronous, unbuffered I/O: asynchio /f*.dat /s /n
        Operations completed in the order issued.
        500 requests queued and completed in 4.495806 seconds.
    

    Questo test dimostra che questo programma ha impiegato 4,495880 secondi chiamando ReadFile per completare le operazioni, ma il test 1 ha impiegato solo 0,224264 secondi per inviare le stesse richieste. Nel test 2, non c'è stato alcun tempo aggiuntivo per il programma per eseguire qualsiasi lavoro in background.

  • Test 3

    Asynchronous, buffered I/O: asynchio /f*.dat
        Operations completed in the order issued.
        500 requests issued and completed in 0.251670 second.
    

    Questo test illustra la natura sincrona della cache. Tutte le letture sono state emesse e completate in 0,251670 secondi. In altre parole, le richieste asincrone sono state completate in modo sincrono. Questo test illustra anche le prestazioni elevate del gestore della cache quando i dati si trova nella cache.

  • Test 4

    Synchronous, buffered I/O: asynchio /f*.dat /s
        Operations completed in the order issued.
        500 requests and completed in 0.217011 seconds.
    

    Questo test mostra gli stessi risultati del test 3. Le letture sincrone dalla cache vengono completate un po' più velocemente rispetto alle letture asincrone dalla cache. Questo test illustra anche le prestazioni elevate del gestore della cache quando i dati si trova nella cache.

Conclusione

È possibile decidere quale metodo è più adatto perché dipende dal tipo, dalle dimensioni e dal numero di operazioni eseguite dal programma.

L'accesso ai file predefinito senza specificare flag speciali a CreateFile è un'operazione sincrona e memorizzata nella cache.

Nota

Si ottiene un comportamento asincrono automatico in questa modalità perché il driver del file system esegue operazioni di lettura in avanti asincrona predittiva e scrittura differita asincrona dei dati modificati. Anche se questo comportamento non rende asincrono l'I/O dell'applicazione, è il caso ideale per la maggior parte delle applicazioni semplici.

D'altra parte, se l'applicazione non è semplice, potrebbe essere necessario eseguire alcune operazioni di profilatura e monitoraggio delle prestazioni per determinare il metodo migliore, simile ai test illustrati in precedenza in questo articolo. La profilatura del tempo impiegato nella funzione o WriteFile e quindi il ReadFile confronto di questo tempo con il tempo necessario per il completamento delle operazioni di I/O effettive è utile. Se la maggior parte del tempo impiegato per eseguire effettivamente l'I/O, l'I/O viene completato in modo sincrono. Tuttavia, se il tempo impiegato per l'emissione di richieste di I/O è relativamente ridotto rispetto al tempo necessario per il completamento delle operazioni di I/O, le operazioni vengono trattate in modo asincrono. Il codice di esempio citato in precedenza in questo articolo usa la QueryPerformanceCounter funzione per eseguire la propria profilatura interna.

Il monitoraggio delle prestazioni consente di determinare l'efficienza con cui il programma usa il disco e la cache. Il rilevamento di uno dei contatori delle prestazioni per l'oggetto Cache indicherà le prestazioni della gestione cache. Il rilevamento dei contatori delle prestazioni per gli oggetti Disco fisico o Disco logico indicherà le prestazioni dei sistemi disco.

Esistono diverse utilità utili per il monitoraggio delle prestazioni. PerfMon e DiskPerf sono particolarmente utili. Per consentire al sistema di raccogliere dati sulle prestazioni dei sistemi su disco, è necessario prima eseguire il DiskPerf comando . Dopo aver eseguito il comando, è necessario riavviare il sistema per avviare la raccolta dati.

Riferimenti

I/O sincrono e asincrono