I/O sincrono e asincrono
Vedere anche applicazioni di esempio correlate all'I/O.
Esistono due tipi di sincronizzazione di input/output (I/O): I/O sincrono e I/O asincroni. L'I/O asincrona viene definita anche I/O sovrapposta.
Nel file sincrono I/O, un thread avvia un'operazione di I/O e passa immediatamente a uno stato di attesa fino al completamento della richiesta di I/O. Un thread che esegue l'I/O di file asincrono invia una richiesta di I/O al kernel chiamando una funzione appropriata. Se la richiesta viene accettata dal kernel, il thread chiamante continua l'elaborazione di un altro processo finché il kernel non segnala al thread che l'operazione di I/O è stata completata. Interrompe quindi il processo corrente ed elabora i dati dall'operazione di I/O in base alle esigenze.
I due tipi di sincronizzazione sono illustrati nella figura seguente.
In situazioni in cui si prevede che una richiesta di I/O richieda molto tempo, ad esempio un aggiornamento o un backup di un database di grandi dimensioni o un collegamento di comunicazione lenta, l'I/O asincrona è in genere un buon modo per ottimizzare l'efficienza di elaborazione. Tuttavia, per operazioni di I/O relativamente veloci, l'overhead dell'elaborazione delle richieste di I/O del kernel e dei segnali del kernel può rendere meno vantaggioso l'I/O asincrono, in particolare se è necessario effettuare molte operazioni di I/O veloci. In questo caso, le I/O sincrone sarebbero migliori. I meccanismi e i dettagli di implementazione su come eseguire queste attività variano a seconda del tipo di handle di dispositivo usato e delle specifiche esigenze dell'applicazione. In altre parole, esistono in genere più modi per risolvere il problema.
Considerazioni sulle operazioni di I/O sincrone e asincrone
Se un file o un dispositivo viene aperto per operazioni di I/O sincrone (ovvero, non viene specificato FILE_FLAG_OVERLAPPED ), le chiamate successive a funzioni come WriteFile possono bloccare l'esecuzione del thread chiamante fino a quando non si verifica uno degli eventi seguenti:
- L'operazione di I/O viene completata (in questo esempio, una scrittura di dati).
- Si è verificato un errore di I/O. Ad esempio, la pipe viene chiusa dall'altra estremità.
- È stato generato un errore nella chiamata stessa (ad esempio, uno o più parametri non sono validi).
- Un altro thread nel processo chiama la funzione CancelSynchronousIo usando l'handle di thread bloccato, che termina l'I/O per tale thread, con esito negativo dell'operazione di I/O.
- Il thread bloccato viene terminato dal sistema; Ad esempio, il processo stesso viene terminato o un altro thread chiama la funzione TerminateThread usando l'handle del thread bloccato. (Questo è in genere considerato un'ultima risorsa e non una buona progettazione di applicazioni.
In alcuni casi, questo ritardo potrebbe essere inaccettabile per la progettazione e lo scopo dell'applicazione, quindi i progettisti di applicazioni devono prendere in considerazione l'uso di operazioni di I/O asincrone con oggetti di sincronizzazione thread appropriati, ad esempio porte di completamento di I/O. Per altre informazioni sulla sincronizzazione dei thread, vedere Informazioni sulla sincronizzazione.
Un processo apre un file per l'I/O asincrona nella chiamata a CreateFile specificando il flag FILE_FLAG_OVERLAPPED nel parametro dwFlagsAndAttributes. Se non viene specificato FILE_FLAG_OVERLAPPED , il file viene aperto per le operazioni di I/O sincrone. Quando il file è stato aperto per l'I/O asincrono, un puntatore a una struttura OVERLAPPED viene passato alla chiamata a ReadFile e WriteFile. Quando si eseguono operazioni di I/O sincrone, questa struttura non è necessaria nelle chiamate a ReadFile e WriteFile.
Nota
Se un file o un dispositivo viene aperto per operazioni di I/O asincrone, le chiamate successive a funzioni come WriteFile che usano tale handle restituiscono in genere immediatamente, ma possono anche comportarsi in modo sincrono rispetto all'esecuzione bloccata. Per altre informazioni, vedere I/O del disco asincrono visualizzato come sincrono in Windows.
Anche se CreateFile è la funzione più comune da usare per l'apertura di file, volumi di dischi, pipe anonime e altri dispositivi simili, le operazioni di I/O possono essere eseguite anche usando un typecast di handle da altri oggetti di sistema, ad esempio un socket creato dal socket o funzioni accept.
Gli handle per gli oggetti directory vengono ottenuti chiamando la funzione CreateFile con l'attributo FILE_FLAG_BACKUP_SEMANTICS. Gli handle di directory non vengono quasi mai usati. Le applicazioni di backup sono una delle poche applicazioni che in genere le useranno.
Dopo aver aperto l'oggetto file per l'I/O asincrona, è necessario creare, inizializzare e passare correttamente una struttura OVERLAPPED a ogni chiamata a funzioni come ReadFile e WriteFile. Quando si usa la struttura OVERLAPPED nelle operazioni asincrone di lettura e scrittura, tenere presente quanto segue:
- Non deallocare o modificare la struttura OVERLAPPED o il buffer di dati fino al completamento di tutte le operazioni di I/O asincrone per l'oggetto file.
- Se si dichiara il puntatore alla struttura OVERLAPPED come variabile locale, non uscire dalla funzione locale finché non vengono completate tutte le operazioni di I/O asincrone all'oggetto file. Se la funzione locale viene chiusa prematuramente, la struttura OVERLAPPED esce dall'ambito e non sarà accessibile alle funzioni ReadFile o WriteFile rilevate all'esterno di tale funzione.
È anche possibile creare un evento e inserire l'handle nella struttura OVERLAPPED . Le funzioni di attesa possono quindi essere usate per attendere il completamento dell'operazione di I/O attendendo il completamento dell'handle dell'evento.
Come indicato in precedenza, quando si usa un handle asincrono, le applicazioni devono prestare attenzione quando liberare le risorse associate a un'operazione di I/O specificata su tale handle. Se l'handle viene deallocato prematuramente, ReadFile o WriteFile potrebbe segnalare erroneamente che l'operazione di I/O è stata completata. Inoltre, la funzione WriteFile restituisce a volte TRUE con un valore GetLastError di ERROR_SUCCESS, anche se usa un handle asincrono (che può anche restituire FALSE con ERROR_IO_PENDING). I programmatori abituati alla progettazione di I/O sincrona in genere rilasciano le risorse del buffer dei dati a questo punto perché TRUE e ERROR_SUCCESS indicano che l'operazione è stata completata. Tuttavia, se le porte di completamento di I/O vengono usate con questo handle asincrono, verrà inviato anche un pacchetto di completamento anche se l'operazione di I/O è stata completata immediatamente. In altre parole, se l'applicazione libera le risorse dopo writeFile restituisce TRUE con ERROR_SUCCESS oltre alla routine della porta di completamento di I/O, avrà una condizione di errore senza doppio valore. In questo esempio, è consigliabile consentire alla routine della porta di completamento di essere esclusivamente responsabile di tutte le operazioni di liberamento per tali risorse.
Il sistema non gestisce il puntatore di file negli handle asincroni ai file e ai dispositivi che supportano i puntatori ai file (ovvero alla ricerca di dispositivi), pertanto la posizione del file deve essere passata alle funzioni di lettura e scrittura nei membri dei dati di offset correlati della struttura OVERLAPPED. Per altre informazioni, vedere WriteFile e ReadFile.
La posizione del puntatore di file per un handle sincrono viene mantenuta dal sistema quando i dati vengono letti o scritti e possono essere aggiornati anche usando la funzione SetFilePointer o SetFilePointerEx.
Un'applicazione può anche attendere l'handle di file per sincronizzare il completamento di un'operazione di I/O, ma questa operazione richiede estrema cautela. Ogni volta che viene avviata un'operazione di I/O, il sistema operativo imposta l'handle di file sullo stato non firmato. Ogni volta che viene completata un'operazione di I/O, il sistema operativo imposta l'handle di file sullo stato segnalato. Pertanto, se un'applicazione avvia due operazioni di I/O e attende l'handle di file, non è possibile determinare quale operazione viene completata quando l'handle è impostato sullo stato segnalato. Se un'applicazione deve eseguire più operazioni di I/O asincrone su un singolo file, deve attendere l'handle dell'evento nella struttura OVERLAPPED specifica per ogni operazione di I/O anziché sull'handle di file comune.
Per annullare tutte le operazioni di I/O asincrone in sospeso, usare:
- CancelIo: questa funzione annulla solo le operazioni eseguite dal thread chiamante per l'handle di file specificato.
- CancelIoEx: questa funzione annulla tutte le operazioni eseguite dai thread per l'handle di file specificato.
Usare CancelSynchronousIo per annullare le operazioni di I/O sincrone in sospeso.
Le funzioni ReadFileEx e WriteFileEx consentono a un'applicazione di specificare una routine da eseguire (vedere FileIOCompletionRoutine) al termine della richiesta di I/O asincrona.