Condividi tramite


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

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

Versione originale del prodotto: Windows
Numero KB originale: 156932

Riepilogo

I/O di file in Microsoft Windows possono essere sincroni o asincroni. 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 asincrona consente a una funzione di I/O di restituire immediatamente l'esecuzione al chiamante, ma l'I/O non verrà completato fino a un certo periodo di tempo futuro. Il sistema operativo invia una notifica al chiamante al termine dell'I/O. Al contrario, il chiamante può 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 tempo per eseguire altre operazioni o inviare più richieste mentre l'operazione di I/O è stata completata. Il termine I/O sovrapposto viene spesso usato per le operazioni di I/O asincrone e I/O non sovrapposte per le operazioni di I/O sincrone. Questo articolo usa i termini asincroni e sincroni per le operazioni di I/O. Questo articolo presuppone che il lettore abbia familiarità con le funzioni di I/O dei file, ad CreateFileesempio , ReadFile, WriteFile.

Spesso, le operazioni di I/O asincrone si comportano esattamente come operazioni di I/O sincrone. Alcune condizioni descritte in questo articolo nelle sezioni successive, che rendono le operazioni di I/O complete 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 a operazioni di I/O sincrone e asincrone. Questo articolo usa ReadFile e WriteFile come esempi. Le alternative valide sono ReadFileEx e WriteFileEx. Anche se questo articolo illustra in modo specifico solo l'I/O del disco, molti dei principi possono essere applicati ad altri tipi di I/O, ad esempio I/O seriale o I/O di rete.

Configurare l'I/O asincrona

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 asincrona perché il sistema si riserva il diritto di effettuare un'operazione sincrona, 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.

Esistono molte operazioni che un programma può eseguire durante l'attesa del completamento di 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 attende 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);

Note

&NumberOfBytesRead passato in ReadFile è diverso da &NumberOfBytesTransferred passato a GetOverlappedResult. Se è stata eseguita un'operazione asincrona, GetOverlappedResult viene usato 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, passa &NumberOfBytesRead a ReadFile è valida 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 inizializzarlo correttamente in modo che nessun dato lasciato 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 asincrona sembra ancora essere sincrona

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

Esistono diversi 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 eseguite in modo sincrono. 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 le operazioni di I/O asincrone in sincrone. 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 è le operazioni stesse. In Windows, qualsiasi operazione di scrittura in un file che ne estende la lunghezza sarà sincrona.

Note

Le applicazioni possono rendere asincrona l'operazione di scrittura indicata in precedenza modificando la lunghezza dati valida del file usando la SetFileValidData funzione e quindi eseguendo un oggetto WriteFile.

Utilizzando 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 riempimento zero.

Poiché il file system NTFS non riempie a zero i dati fino alla lunghezza dei dati valida (VDL) definita da SetFileValidData, questa funzione ha implicazioni di sicurezza in cui il file può essere assegnato a 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) dispone di 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ò essere utile se i dati non si trovano nella cache. La cache di Windows viene implementata internamente tramite mapping di file. Gestione memoria in Windows non fornisce un meccanismo di errore di pagina asincrono per gestire i mapping dei file usati da Gestione cache. Gestione cache può verificare se la pagina richiesta è in memoria, quindi se si rilascia una lettura memorizzata nella cache asincrona e le pagine non sono in memoria, il driver del file system presuppone che il thread non sia 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), ci saranno ancora solo alcune richieste accodate al driver del disco in un determinato momento. Se si eseguono numerose operazioni di I/O per i dati che non si trovano nella cache, gestione cache e gestione memoria diventano saturi e le richieste vengono effettuate sincrone.

Il comportamento della gestione cache può anche essere influenzato in base all'accesso a un file in modo sequenziale o casuale. La maggior parte dei vantaggi della cache è visibile 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 al gestore della cache di ottimizzare 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.

Note

Esistono alcune restrizioni all'uso di questo flag che hanno a che fare con l'allineamento del buffer dei 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 reali

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 precedentemente menzionato 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 trascorso 4,495880 secondi chiamando ReadFile per completare le operazioni, ma il test 1 ha speso solo 0,224264 secondi per inviare le stesse richieste. Nel test 2 non c'era 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 rilasciate 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 cache quando i dati si trovano 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 illustra gli stessi risultati del test 3. Le letture sincrone dalla cache vengono completate un po' più velocemente delle letture asincrone dalla cache. Questo test illustra anche le prestazioni elevate del gestore cache quando i dati si trovano nella cache.

Conclusione

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

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

Note

In questa modalità si ottiene un comportamento asincrono automatico perché il driver del file system esegue la scrittura asincrona di dati modificati e asincrona. Anche se questo comportamento non rende asincrona 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 ReadFile funzione o WriteFile e quindi il confronto di questo tempo con il tempo necessario per il completamento effettivo delle operazioni di I/O è utile. Se la maggior parte del tempo viene effettivamente impiegato per l'emissione dell'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 menzionato in precedenza in questo articolo usa la QueryPerformanceCounter funzione per eseguire la propria profilatura interna.

Il monitoraggio delle prestazioni consente di determinare in modo efficiente il programma usando il disco e la cache. Il rilevamento di uno dei contatori delle prestazioni per l'oggetto Cache indicherà le prestazioni di Gestione cache. Tenere traccia dei contatori delle prestazioni per gli oggetti Disco fisico o Disco logico indicherà le prestazioni dei sistemi su disco.

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

Riferimenti

I/O sincrono e asincrono