Synchrone und asynchrone E/A
Siehe auch E/A-bezogene Beispielanwendungen.
Es gibt zwei Arten der Ein-/Ausgabesynchronisation (E/A): synchrone E/A und asynchrone E/A. Asynchrone E/A wird auch als überlappende E/A bezeichnet.
Bei der synchronen Datei-E/A startet ein Thread einen E/A-Vorgang und geht sofort in einen Wartezustand über, bis die E/A-Anforderung abgeschlossen ist. Ein Thread, der asynchrone Datei-E/A durchführt, sendet eine E/A-Anforderung an den Kernel, indem er eine entsprechende Funktion aufruft. Wenn die Anforderung vom Kernel akzeptiert wird, fährt der aufrufende Thread mit der Bearbeitung eines anderen Auftrags fort, bis der Kernel dem Thread signalisiert, dass die E/A-Operation abgeschlossen ist. Er unterbricht dann seinen aktuellen Auftrag und verarbeitet die Daten aus der E/A-Operation wie erforderlich.
Die beiden Synchronisationsarten sind in der folgenden Abbildung dargestellt.
In Situationen, in denen eine E/A-Anforderung voraussichtlich viel Zeit in Anspruch nehmen wird, z. B. bei der Aktualisierung oder Sicherung einer großen Datenbank oder bei einer langsamen Kommunikationsverbindung, ist die asynchrone E/A im Allgemeinen eine gute Möglichkeit, die Verarbeitungseffizienz zu optimieren. Bei relativ schnellen E/A-Operationen kann der Overhead der Verarbeitung von Kernel-E/A-Anforderungen und Kernel-Signalen jedoch dazu führen, dass die asynchrone E/A weniger vorteilhaft ist, insbesondere wenn viele schnelle E/A-Operationen durchgeführt werden müssen. In diesem Fall wäre eine synchrone E/A besser. Die Mechanismen und Implementierungsdetails zur Bewältigung dieser Aufgaben hängen von der Art des verwendeten Gerätehandles und den besonderen Anforderungen der Anwendung ab. Mit anderen Worten: Es gibt in der Regel mehrere Möglichkeiten, das Problem zu lösen.
Überlegungen zu synchronen und asynchronen E/A
Wenn eine Datei oder ein Gerät für synchrone E/A geöffnet ist (d. h. FILE_FLAG_OVERLAPPED ist nicht angegeben), können nachfolgende Anfragen an Funktionen wie WriteFile die Ausführung des aufrufenden Threads blockieren, bis eines der folgenden Ereignisse eintritt:
- Die E/A-Operation ist abgeschlossen (in diesem Beispiel ein Schreiben von Daten).
- Ein E/A-Fehler tritt auf. (Beispielsweise wird die Pipe vom anderen Ende her verschlossen.)
- Bei der Anfrage selbst ist ein Fehler aufgetreten (z. B. ein oder mehrere Parameter sind ungültig).
- Ein anderer Thread im Prozess fragt die Funktion CancelSynchronousIo mit dem Thread-Handle des blockierten Threads an, wodurch die E/A für diesen Thread beendet wird und der E/A-Vorgang fehlschlägt.
- Der blockierte Thread wird vom System beendet, z. B. indem der Prozess selbst beendet wird oder indem ein anderer Thread die Funktion TerminateThread mit dem Handle des blockierten Threads anfragt. (Dies wird im Allgemeinen als letzter Ausweg und nicht als gutes Anwendungsdesign betrachtet).
In einigen Fällen kann diese Verzögerung für das Design und den Zweck der Anwendung inakzeptabel sein. Daher sollten Anwendungsentwickler die Verwendung asynchroner E/A mit geeigneten Thread-Synchronisationsobjekten wie E/A-Abschlussports in Betracht ziehen. Weitere Informationen über die Synchronisierung von Threads finden Sie unter Über Synchronisierung.
Ein Prozess öffnet in seiner Anfrage an CreateFile eine Datei für asynchrone E/A, indem er das Flag FILE_FLAG_OVERLAPPED im Parameter dwFlagsAndAttributes angibt. Wenn FILE_FLAG_OVERLAPPED nicht angegeben ist, wird die Datei für synchrone E/A geöffnet. Wenn die Datei für asynchrone E/A geöffnet wurde, wird ein Zeiger auf eine OVERLAPPED-Struktur an die Anfrage an ReadFile und WriteFile übergeben. Bei synchroner E/A wird diese Struktur in Anfragen an ReadFile und WriteFile nicht benötigt.
Hinweis
Wenn eine Datei oder ein Gerät für asynchrone E/A geöffnet ist, kehren nachfolgende Anfragen an Funktionen wie WriteFile, die dieses Handle verwenden, in der Regel sofort zurück, können sich aber auch in Bezug auf eine blockierte Ausführung synchron verhalten. Weitere Informationen finden Sie unter Asynchrone Festplatten-E/A erscheint unter Windows als synchron.
Obwohl CreateFile die gebräuchlichste Funktion zum Öffnen von Dateien, Plattenlaufwerken, anonymen Pipes und anderen ähnlichen Geräten ist, können E/A-Operationen auch mit einem Typecast-Handle durchgeführt werden, das von anderen Systemobjekten wie einem Socket stammt, der mit den Funktionen socket oder accept erstellt wurde.
Handles für Verzeichnisobjekte erhalten Sie, indem Sie die Funktion CreateFile mit dem Attribut FILE_FLAG_BACKUP_SEMANTICS anfragen. Verzeichnis-Handles werden fast nie verwendet – Backup-Anwendungen sind eine der wenigen Anwendungen, die sie in der Regel verwenden.
Nach dem Öffnen des Dateiobjekts für asynchrone E/A muss eine OVERLAPPED-Struktur ordnungsgemäß erstellt, initialisiert und an jede Anfrage an Funktionen wie ReadFile und WriteFile übergeben werden. Beachten Sie Folgendes, wenn Sie die Struktur OVERLAPPED bei asynchronen Lese- und Schreibvorgängen verwenden:
- Heben Sie die Zuweisung der OVERLAPPED-Struktur oder des Datenpuffers nicht auf und ändern Sie sie nicht, bevor alle asynchronen E/A-Operationen mit dem Dateiobjekt abgeschlossen sind.
- Wenn Sie Ihren Pointer auf die OVERLAPPED-Struktur als lokale Variable definieren, verlassen Sie die lokale Funktion erst, wenn alle asynchronen E/A-Operationen mit dem Dateiobjekt abgeschlossen sind. Wenn die lokale Funktion vorzeitig beendet wird, geht die OVERLAPPED-Struktur aus dem Anwendungsbereich heraus und ist für alle ReadFile- oder WriteFile-Funktionen, die außerhalb dieser Funktion auf sie treffen, unzugänglich.
Sie können auch ein Ereignis erstellen und das Handle in die OVERLAPPED-Struktur stellen. Die Wartefunktionen können dann verwendet werden, um auf den Abschluss der E/A-Operation zu warten, indem Sie auf das Ereignis-Handle warten.
Wie bereits erwähnt, sollten Anwendungen bei der Arbeit mit einem asynchronen Handle vorsichtig sein, wenn sie entscheiden, wann die mit einer bestimmten E/A-Operation an diesem Handle verbundenen Ressourcen freigegeben werden sollen. Wenn das Handle vorzeitig freigegeben wird, kann ReadFile oder WriteFile fälschlicherweise melden, dass der E/A-Vorgang abgeschlossen ist. Außerdem gibt die Funktion WriteFile manchmal TRUE mit einem GetLastError-Wert von ERROR_SUCCESS zurück, obwohl sie ein asynchrones Handle verwendet (das auch FALSE mit ERROR_IO_PENDING zurückgeben kann). Programmierer, die an das synchrone E/A-Design gewöhnt sind, geben an dieser Stelle in der Regel die Datenpufferressourcen frei, da TRUE und ERROR_SUCCESS bedeuten, dass die Operation abgeschlossen ist. Wenn jedoch E/A-Abschlussports mit diesem asynchronen Handle verwendet werden, wird auch ein Abschlusspaket gesendet, obwohl der E/A-Vorgang sofort abgeschlossen wurde. Mit anderen Worten, wenn die Anwendung Ressourcen freigibt, nachdem WriteFile mit ERROR_SUCCESS TRUE zurückgegeben hat, und zwar zusätzlich zur I/O Completion Port Routine, hat sie eine doppelt-freie Fehlerbedingung. In diesem Beispiel würde die Empfehlung lauten, der Completion Port Routine die alleinige Verantwortung für alle Freigabeoperationen für solche Ressourcen zu übertragen.
Das System verwaltet den File Pointer nicht bei asynchronen Handles zu Dateien und Geräten, die File Pointer unterstützen (d.h. suchende Geräte). Daher muss die Dateiposition an die Lese- und Schreibfunktionen in den entsprechenden Offset-Datenelementen der OVERLAPPED-Struktur übergeben werden. Weitere Informationen finden Sie unter WriteFile und ReadFile.
Die Position des File Pointers für ein synchrones Handle wird vom System beibehalten, wenn Daten gelesen oder geschrieben werden und kann auch mit der Funktion SetFilePointer oder SetFilePointerEx aktualisiert werden.
Eine Anwendung kann auch auf das Dateihandle warten, um den Abschluss einer E/A-Operation zu synchronisieren, aber dabei ist äußerste Vorsicht geboten. Jedes Mal, wenn eine E/A-Operation gestartet wird, setzt das Betriebssystem das Dateihandle in den nicht signalisierten Zustand. Jedes Mal, wenn ein E/A-Vorgang abgeschlossen ist, setzt das Betriebssystem das Dateihandle in den signalisierten Zustand. Wenn eine Anwendung also zwei E/A-Operationen startet und auf das Dateihandle wartet, gibt es keine Möglichkeit festzustellen, welche Operation beendet ist, wenn das Handle in den signalisierten Zustand versetzt wird. Wenn eine Anwendung mehrere asynchrone E/A-Operationen an einer einzigen Datei durchführen muss, sollte sie auf das Ereignis-Handle in der spezifischen OVERLAPPED-Struktur für jede E/A-Operation warten und nicht auf das gemeinsame Datei-Handle.
Um alle anstehenden asynchronen E/A-Operationen abzubrechen, verwenden Sie entweder:
- CancelIo – Diese Funktion bricht nur die Operationen ab, die der aufrufende Thread für das angegebene Dateihandle ausführt.
- CancelIoEx – Diese Funktion bricht alle Operationen ab, die von den Threads für das angegebene Dateihandle ausgeführt werden.
Verwenden Sie CancelSynchronousIo, um ausstehende synchrone E/A-Operationen abzubrechen.
Mit den Funktionen ReadFileEx und WriteFileEx kann eine Anwendung eine Routine angeben, die ausgeführt wird (siehe FileIOCompletionRoutine), wenn die asynchrone E/A-Anforderung abgeschlossen ist.