Synchrone und asynchrone E/A

Siehe auch E/A-bezogene Beispielanwendungen.

Es gibt zwei Arten der Eingabe-/Ausgabesynchronisierung (E/A): synchrone E/A und asynchrone E/A. Asynchrone E/A wird auch als überlappende E/A bezeichnet.

Bei synchroner Datei-E/A startet ein Thread einen E/A-Vorgang und wechselt sofort in einen Wartezustand, bis die E/A-Anforderung abgeschlossen ist. Ein Thread, der eine asynchrone Datei-E/A ausführt, sendet eine E/A-Anforderung an den Kernel, indem eine entsprechende Funktion aufgerufen wird. Wenn die Anforderung vom Kernel akzeptiert wird, setzt der aufrufende Thread die Verarbeitung eines anderen Auftrags fort, bis der Kernel dem Thread signalisiert, dass der E/A-Vorgang abgeschlossen ist. Anschließend unterbricht er seinen aktuellen Auftrag und verarbeitet die Daten aus dem E/A-Vorgang nach Bedarf.

Die beiden Synchronisierungstypen sind in der folgenden Abbildung dargestellt.

synchrone und asynchrone E/A-Vorgänge

In Situationen, in denen erwartet wird, dass eine E/A-Anforderung viel Zeit in Anspruch nimmt, z. B. eine Aktualisierung oder Sicherung einer großen Datenbank oder eine langsame Kommunikationsverbindung, ist asynchrone E/A im Allgemeinen eine gute Möglichkeit, die Verarbeitungseffizienz zu optimieren. Bei relativ schnellen E/A-Vorgängen kann jedoch der Mehraufwand für die Verarbeitung von Kernel-E/A-Anforderungen und Kernelsignalen die asynchrone E/A weniger vorteilhaft machen, insbesondere wenn viele schnelle E/A-Vorgänge durchgeführt werden müssen. In diesem Fall wäre synchrone E/A besser. Die Mechanismen und Implementierungsdetails zum Ausführen dieser Aufgaben variieren je nach Typ des verwendeten Gerätehandles und den spezifischen Anforderungen der Anwendung. Anders ausgedrückt: Es gibt in der Regel mehrere Möglichkeiten, das Problem zu lösen.

Überlegungen zu synchroner und asynchroner E/A

Wenn eine Datei oder ein Gerät für synchrone E/A geöffnet wird (d. h. FILE_FLAG_OVERLAPPED nicht angegeben ist), können nachfolgende Aufrufe von Funktionen wie WriteFile die Ausführung des aufrufenden Threads blockieren, bis eines der folgenden Ereignisse eintritt:

  • Der E/A-Vorgang wird abgeschlossen (in diesem Beispiel ein Datenschreibvorgang).
  • Ein E/A-Fehler tritt auf. (Beispielsweise wird die Pipe vom anderen Ende geschlossen.)
  • Im Aufruf selbst ist ein Fehler aufgetreten (z. B. sind mindestens ein Parameter ungültig).
  • Ein anderer Thread im Prozess ruft die CancelSynchronousIo-Funktion mithilfe des Threadhandles des blockierten Threads auf, das die E/A für diesen Thread beendet und der E/A-Vorgang fehlschlägt.
  • Der blockierte Thread wird vom System beendet. Beispielsweise wird der Prozess selbst beendet, oder ein anderer Thread ruft die TerminateThread-Funktion mithilfe des Handles des blockierten Threads auf. (Dies gilt im Allgemeinen als letzter Ausweg und nicht als gutes Anwendungsdesign.)

In einigen Fällen kann diese Verzögerung für den Entwurf und zweck der Anwendung inakzeptabel sein. Daher sollten Anwendungsdesigner die Verwendung asynchroner E/A-Vorgänge mit geeigneten Threadsynchronisierungsobjekten wie E/A-Vervollständigungsports in Betracht ziehen. Weitere Informationen zur Threadsynchronisierung finden Sie unter Informationen zur Synchronisierung.

Ein Prozess öffnet eine Datei für asynchrone E/A in seinem Aufruf von CreateFile , indem er das flag FILE_FLAG_OVERLAPPED im dwFlagsAndAttributes-Parameter angibt . Wenn FILE_FLAG_OVERLAPPED nicht angegeben ist, wird die Datei für synchrone E/A-Vorgänge geöffnet. Wenn die Datei für asynchrone E/A geöffnet wurde, wird ein Zeiger auf eine OVERLAPPED-Struktur an den Aufruf von ReadFile und WriteFile übergeben. Bei synchronen E/A-Vorgängen ist diese Struktur in Aufrufen von ReadFile und WriteFile nicht erforderlich.

Hinweis

Wenn eine Datei oder ein Gerät für asynchrone E/A geöffnet wird, werden nachfolgende Aufrufe von Funktionen wie WriteFile mit diesem Handle in der Regel sofort zurückgegeben, können sich aber auch synchron im Hinblick auf die blockierte Ausführung verhalten. Weitere Informationen finden Sie unter https://support.microsoft.com/kb/156932.

 

Obwohl CreateFile die am häufigsten verwendete Funktion zum Öffnen von Dateien, Datenträgervolumes, anonymen Pipes und anderen ähnlichen Geräten ist, können E/A-Vorgänge auch mithilfe eines Handle-Typecasts von anderen Systemobjekten ausgeführt werden, z. B. einem Socket, der vom Socket erstellt wurde, oder accept-Funktionen .

Handles für Verzeichnisobjekte werden durch Aufrufen der CreateFile-Funktion mit dem FILE_FLAG_BACKUP_SEMANTICS-Attribut abgerufen. Verzeichnishandles werden fast nie verwendet. Sicherungsanwendungen 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 jeden Aufruf von Funktionen wie ReadFile und WriteFile übergeben werden. Beachten Sie Folgendes, wenn Sie die OVERLAPPED-Struktur in asynchronen Lese- und Schreibvorgängen verwenden:

  • Heben Sie die Zuordnung der OVERLAPPED-Struktur oder des Datenpuffers nicht auf, bis alle asynchronen E/A-Vorgänge zum Dateiobjekt abgeschlossen sind.
  • Wenn Sie den Zeiger auf die OVERLAPPED-Struktur als lokale Variable deklarieren, beenden Sie die lokale Funktion erst, wenn alle asynchronen E/A-Vorgänge für das Dateiobjekt abgeschlossen sind. Wenn die lokale Funktion vorzeitig beendet wird, wird die OVERLAPPED-Struktur aus dem Gültigkeitsbereich entfernt und kann nicht auf alle ReadFile - oder WriteFile-Funktionen zugegriffen werden, auf die sie außerhalb dieser Funktion stößt.

Sie können auch ein Ereignis erstellen und das Handle in die OVERLAPPED-Struktur einfügen. Die Wartefunktionen können dann verwendet werden, um auf den Abschluss des E/A-Vorgangs zu warten, indem auf das Ereignishandle gewartet wird.

Wie bereits erwähnt, sollten Anwendungen bei der Arbeit mit einem asynchronen Handle sorgfältig festlegen, wann Ressourcen freigegeben werden sollen, die einem angegebenen E/A-Vorgang für dieses Handle zugeordnet sind. Wenn die Zuordnung des Handles vorzeitig aufgehoben wird, meldet ReadFile oder WriteFile möglicherweise fälschlicherweise, dass der E/A-Vorgang abgeschlossen ist. Darüber hinaus gibt die WriteFile-Funktion manchmal TRUE mit dem GetLastError-WertERROR_SUCCESS zurück, obwohl sie ein asynchrones Handle verwendet (das auch FALSE mit ERROR_IO_PENDING zurückgeben kann). Programmierer, die an den synchronen E/A-Entwurf gewöhnt sind, geben in der Regel Datenpufferressourcen zu diesem Zeitpunkt frei, da TRUE und ERROR_SUCCESS bedeuten, dass der Vorgang abgeschlossen ist. Wenn jedoch E/A-Vervollständigungsports mit diesem asynchronen Handle verwendet werden, wird auch ein Abschlusspaket gesendet, obwohl der E/A-Vorgang sofort abgeschlossen wurde. Anders ausgedrückt: Wenn die Anwendung Ressourcen freigibt, nachdem WriteFiletrue mit ERROR_SUCCESS zusätzlich zu in der E/A-Vervollständigungs-Portroutine zurückgegeben hat, hat sie eine doppelte Fehlerbedingung. In diesem Beispiel lautet die Empfehlung, dass die Abschlussportroutine allein für alle Freigabevorgänge für solche Ressourcen verantwortlich ist.

Das System verwaltet den Dateizeiger nicht auf asynchronen Handles für Dateien und Geräte, die Dateizeiger unterstützen (d. h. suchende Geräte). Daher muss die Dateiposition an die Lese- und Schreibfunktionen in den verknüpften Offsetdatenmembern der OVERLAPPED-Struktur übergeben werden. Weitere Informationen finden Sie unter WriteFile und ReadFile.

Die Position des Dateizeigers für ein synchrones Handle wird vom System verwaltet, wenn Daten gelesen oder geschrieben werden, und sie kann auch mithilfe der SetFilePointer - oder SetFilePointerEx-Funktion aktualisiert werden.

Eine Anwendung kann auch auf das Dateihandle warten, um den Abschluss eines E/A-Vorgangs zu synchronisieren. Dies erfordert jedoch äußerste Vorsicht. Jedes Mal, wenn ein E/A-Vorgang gestartet wird, legt das Betriebssystem das Dateihandle auf den nicht signalierten Zustand fest. Jedes Mal, wenn ein E/A-Vorgang abgeschlossen ist, legt das Betriebssystem das Dateihandle auf den signalierten Zustand fest. Wenn eine Anwendung also zwei E/A-Vorgänge startet und auf das Dateihandle wartet, gibt es keine Möglichkeit, zu bestimmen, welcher Vorgang abgeschlossen ist, wenn das Handle auf den signalierten Zustand festgelegt ist. Wenn eine Anwendung mehrere asynchrone E/A-Vorgänge für eine einzelne Datei ausführen muss, sollte sie auf das Ereignishandle in der spezifischen OVERLAPPED-Struktur für jeden E/A-Vorgang und nicht auf das allgemeine Dateihandle warten.

Verwenden Sie einen der folgenden Optionen, um alle ausstehenden asynchronen E/A-Vorgänge abzubrechen:

  • CancelIo: Diese Funktion bricht nur Vorgänge ab, die vom aufrufenden Thread für das angegebene Dateihandle ausgegeben wurden.
  • CancelIoEx: Diese Funktion bricht alle Vorgänge ab, die von den Threads für das angegebene Dateihandle ausgegeben werden.

Verwenden Sie CancelSynchronousIo , um ausstehende synchrone E/A-Vorgänge abzubrechen.

Mit den Funktionen ReadFileEx und WriteFileEx kann eine Anwendung eine Routine angeben, die ausgeführt werden soll (siehe FileIOCompletionRoutine), wenn die asynchrone E/A-Anforderung abgeschlossen ist.