Abbrechen ausstehender E/A-Vorgänge

Benutzern zu erlauben, E/A-Anforderungen, die langsam oder blockiert sind, zu stornieren, kann die Benutzerfreundlichkeit und Stabilität Ihrer Anwendung verbessern. Wenn z. B. ein Aufruf der OpenFile-Funktion blockiert wird, weil der Aufruf an ein sehr langsames Gerät erfolgt, ermöglicht das Abbrechen, dass der Aufruf erneut mit neuen Parametern ausgeführt wird, ohne die Anwendung zu beenden.

Windows Vista erweitert die Abbruchfunktionen und bietet Unterstützung für das Abbrechen synchroner Vorgänge.

Hinweis

Das Aufrufen der CancelIoEx-Funktion garantiert nicht, dass ein E/A-Vorgang abgebrochen wird. Der Treiber, der den Vorgang verarbeitet, muss den Abbruch unterstützen, und der Vorgang muss sich in einem Zustand befinden, der abgebrochen werden kann.

Überlegungen zum Abbruch

Beachten Sie bei Aufrufen des Programmierabbruchs die folgenden Überlegungen:

  • Es gibt keine Garantie, dass zugrunde liegende Treiber den Abbruch ordnungsgemäß unterstützen.
  • Wenn beim Abbrechen der asynchronen E/A keine überlappende Struktur für die CancelIoEx-Funktion bereitgestellt wird, versucht die Funktion, alle ausstehenden E/A-Vorgänge für die Datei für alle Threads im Prozess abzubrechen. Jeder Thread wird einzeln verarbeitet, sodass nach der Verarbeitung eines Threads möglicherweise ein weiterer E/A-Vorgang für die Datei gestartet wird, bevor die E/A-Vorgänge für die Datei für alle anderen Threads abgebrochen wurden, was zu Synchronisierungsproblemen führt.
  • Verwenden Sie beim Abbrechen der asynchronen E/A keine überlappenden Strukturen mit gezieltem Abbruch. Sobald der E/A-Vorgang abgeschlossen ist (entweder erfolgreich oder mit einem abgebrochenen status), wird die überlappende Struktur nicht mehr vom System verwendet und kann wiederverwendet werden.
  • Beim Abbrechen der synchronen E/A versucht der Aufruf der CancelSynchronousIo-Funktion , jeden aktuellen synchronen Aufruf im Thread abzubrechen. Sie müssen darauf achten, dass die Synchronisierung der Aufrufe korrekt ist. Der falsche Anruf in einer Reihe von Anrufen kann abgebrochen werden. Wenn z. B. die CancelSynchronousIo-Funktion für einen synchronen Vorgang (X) aufgerufen wird, wird der Vorgang Y erst gestartet, nachdem dieser Vorgang X abgeschlossen wurde (normalerweise oder mit einem Fehler). Wenn der Thread, der Vorgang X aufgerufen hat, dann einen weiteren synchronen Aufruf von X startet, kann der Abbruchaufruf diese neue E/A-Anforderung unterbrechen.
  • Beachten Sie beim Abbrechen der synchronen E/A, dass eine Racebedingung vorhanden sein kann, wenn ein Thread zwischen verschiedenen Teilen einer Anwendung gemeinsam genutzt wird, z. B. mit einem Threadpoolthread.

Vorgänge, die nicht abgebrochen werden können

Einige Funktionen können nicht mit der Funktion CancelIo, CancelIoEx oder CancelSynchronousIo abgebrochen werden. Einige dieser Funktionen wurden erweitert, um den Abbruch zu ermöglichen (z. B. die CopyFileEx-Funktion ), und Sie sollten stattdessen diese verwenden. Zusätzlich zur Unterstützung des Abbruchs verfügen diese Funktionen auch über integrierte Rückrufe, die Sie beim Nachverfolgen des Fortschritts des Vorgangs unterstützen. Die folgenden Funktionen unterstützen den Abbruch nicht:

Weitere Informationen finden Sie unter E/A-Vervollständigung/Abbruchrichtlinien.

Abbrechen asynchroner E/A-Vorgänge

Sie können die asynchrone E/A von jedem Thread im Prozess abbrechen, der den E/A-Vorgang ausgestellt hat. Sie müssen das Handle angeben, für das die E/A ausgeführt wurde, und optional die überlappende Struktur, die zum Ausführen der E/A verwendet wurde. Sie können ermitteln, ob der Abbruch aufgetreten ist, indem Sie die status untersuchen, die in der überlappenden Struktur oder im Abschlussrückruf zurückgegeben werden. Eine status ERROR_OPERATION_ABORTED gibt an, dass der Vorgang abgebrochen wurde.

Das folgende Beispiel zeigt eine Routine, die ein Timeout annimmt und einen Lesevorgang versucht und mit der CancelIoEx-Funktion abbricht, wenn das Timeout abläuft.

#include <windows.h>

BOOL DoCancelableRead(HANDLE hFile,
                 LPVOID lpBuffer,
                 DWORD nNumberOfBytesToRead,
                 LPDWORD lpNumberOfBytesRead,
                 LPOVERLAPPED lpOverlapped,
                 DWORD dwTimeout,
                 LPBOOL pbCancelCalled)
//
// Parameters:
//
//      hFile - An open handle to a readable file or device.
//
//      lpBuffer - A pointer to the buffer to store the data being read.
//
//      nNumberOfBytesToRead - The number of bytes to read from the file or 
//          device. Must be less than or equal to the actual size of
//          the buffer referenced by lpBuffer.
//
//      lpNumberOfBytesRead - A pointer to a DWORD to receive the number 
//          of bytes read after all I/O is complete or canceled.
//
//      lpOverlapped - A pointer to a preconfigured OVERLAPPED structure that
//          has a valid hEvent.
//          If the caller does not properly initialize this structure, this
//          routine will fail.
//
//      dwTimeout - The desired time-out, in milliseconds, for the I/O read.
//          After this time expires, the I/O is canceled.
// 
//      pbCancelCalled - A pointer to a Boolean to notify the caller if this
//          routine attempted to perform an I/O cancel.
//
// Return Value:
//
//      TRUE on success, FALSE on failure.
//
{
    BOOL result;
    DWORD waitTimer;
    BOOL bIoComplete = FALSE;
    const DWORD PollInterval = 100; // milliseconds

    // Initialize "out" parameters
    *pbCancelCalled = FALSE;
    *lpNumberOfBytesRead = 0;

    // Perform the I/O, in this case a read operation.
    result = ReadFile(hFile,
                  lpBuffer,
                  nNumberOfBytesToRead,
                  lpNumberOfBytesRead,
                  lpOverlapped );

    if (result == FALSE) 
    {
        if (GetLastError() != ERROR_IO_PENDING) 
        {
            // The function call failed. ToDo: Error logging and recovery.
            return FALSE; 
        }
    } 
    else 
    {
        // The I/O completed, done.
        return TRUE;
    }
        
    // The I/O is pending, so wait and see if the call times out.
    // If so, cancel the I/O using the CancelIoEx function.

    for (waitTimer = 0; waitTimer < dwTimeout; waitTimer += PollInterval)
    {
        result = GetOverlappedResult( hFile, lpOverlapped, lpNumberOfBytesRead, FALSE );
        if (result == FALSE)
        {
            if (GetLastError() != ERROR_IO_PENDING)
            {
                // The function call failed. ToDo: Error logging and recovery.
                return FALSE;
            }
            Sleep(PollInterval);
        }
        else
        {
            bIoComplete = TRUE;
            break;
        }
    }

    if (bIoComplete == FALSE) 
    {
        result = CancelIoEx( hFile, lpOverlapped );
        
        *pbCancelCalled = TRUE;

        if (result == TRUE || GetLastError() != ERROR_NOT_FOUND) 
        {
            // Wait for the I/O subsystem to acknowledge our cancellation.
            // Depending on the timing of the calls, the I/O might complete with a
            // cancellation status, or it might complete normally (if the ReadFile was
            // in the process of completing at the time CancelIoEx was called, or if
            // the device does not support cancellation).
            // This call specifies TRUE for the bWait parameter, which will block
            // until the I/O either completes or is canceled, thus resuming execution, 
            // provided the underlying device driver and associated hardware are functioning 
            // properly. If there is a problem with the driver it is better to stop 
            // responding here than to try to continue while masking the problem.

            result = GetOverlappedResult( hFile, lpOverlapped, lpNumberOfBytesRead, TRUE );

            // ToDo: check result and log errors. 
        }
    }

    return result;
}

Abbrechen synchroner E/A-Vorgänge

Sie können synchrone E/A-Vorgänge für jeden Thread des Prozesses abbrechen, der den E/A-Vorgang ausgegeben hat. Sie müssen das Handle für den Thread angeben, der derzeit den E/A-Vorgang ausführt.

Das folgende Beispiel zeigt zwei Routinen:

  • Die SynchronousIoWorker-Funktion ist ein Workerthread, der einige synchrone Datei-E/A-Vorgänge implementiert, beginnend mit einem Aufruf der CreateFile-Funktion . Wenn die Routine erfolgreich ist, können auf die Routine zusätzliche Vorgänge folgen, die hier nicht enthalten sind. Die globale Variable gCompletionStatus kann verwendet werden, um zu bestimmen, ob alle Vorgänge erfolgreich waren oder ob ein Vorgang fehlgeschlagen oder abgebrochen wurde. Die globale Variable dwOperationInProgress gibt an, ob die Datei-E/A noch ausgeführt wird.

    Hinweis

    In diesem Beispiel könnte der UI-Thread auch überprüfen, ob der Workerthread vorhanden ist.

    Zusätzliche manuelle Überprüfungen, die hier nicht enthalten sind, sind in der SynchronousIoWorker-Funktion erforderlich, um sicherzustellen, dass die restlichen Vorgänge abgebrochen werden, wenn der Abbruch während der kurzen Zeiträume zwischen Datei-E/A-Aufrufen angefordert wurde.

  • Die MainUIThreadMessageHandler-Funktion simuliert den Nachrichtenhandler innerhalb der Fensterprozedur eines UI-Threads. Der Benutzer fordert eine Reihe synchroner Dateivorgänge an, indem er auf ein Steuerelement klickt, das eine benutzerdefinierte Fenstermeldung generiert (im Abschnitt, der durch WM_MYSYNCOPS gekennzeichnet ist). Dadurch wird ein neuer Thread mit der CreateFileThread-Funktion erstellt, die dann die SynchronousIoWorker-Funktion startet. Der UI-Thread verarbeitet weiterhin Nachrichten, während der Workerthread die angeforderten E/A-Vorgänge ausführt. Wenn der Benutzer entscheidet, die nicht abgeschlossenen Vorgänge abzubrechen (in der Regel durch Klicken auf eine Schaltfläche zum Abbrechen), ruft die Routine (in dem durch WM_MYCANCEL gekennzeichneten Abschnitt) die CancelSynchronousIo-Funktion mithilfe des von der CreateFileThread-Funktion zurückgegebenen Threadhandles auf. Die CancelSynchronousIo-Funktion wird unmittelbar nach dem Abbruchversuch zurückgegeben. Schließlich kann der Benutzer oder die Anwendung später einen anderen Vorgang anfordern, der davon abhängt, ob die Dateivorgänge abgeschlossen wurden. In diesem Fall überprüft die Routine (im durch WM_PROCESSDATA gekennzeichneten Abschnitt) zunächst, ob die Vorgänge abgeschlossen wurden, und führt dann die sauber-Up-Vorgänge aus.

    Hinweis

    Da der Abbruch in diesem Beispiel an einer beliebigen Stelle in der Abfolge der Vorgänge aufgetreten sein könnte, muss der Aufrufer möglicherweise sicherstellen, dass der Zustand konsistent ist oder zumindest verstanden wird, bevor er fortfahren kann.

// User-defined codes for the message-pump, which is outside the scope 
// of this sample. Windows messaging and message pumps are well-documented
// elsewhere.
#define WM_MYSYNCOPS    1
#define WM_MYCANCEL     2
#define WM_PROCESSDATA  3

VOID SynchronousIoWorker( VOID *pv )
{
    LPCSTR lpFileName = (LPCSTR)pv;
    HANDLE hFile;
    g_dwOperationInProgress = TRUE;    
    g_CompletionStatus = ERROR_SUCCESS;
     
    hFile = CreateFileA(lpFileName,
                        GENERIC_READ,
                        0,
                        NULL,
                        OPEN_EXISTING,
                        0,
                        NULL);


    if (hFile != INVALID_HANDLE_VALUE) 
    {
        BOOL result = TRUE;
        // TODO: CreateFile succeeded. 
        // Insert your code to make more synchronous calls with hFile.
        // The result variable is assumed to act as the error flag here,
        // but can be whatever your code needs.
        
        if (result == FALSE) 
        {
            // An operation failed or was canceled. If it was canceled,
            // GetLastError() returns ERROR_OPERATION_ABORTED.

            g_CompletionStatus = GetLastError();            
        }
             
        CloseHandle(hFile);
    } 
    else 
    {
        // CreateFile failed or was canceled. If it was canceled,
        // GetLastError() returns ERROR_OPERATION_ABORTED.
        g_CompletionStatus = GetLastError();
    }

    g_dwOperationInProgress = FALSE;
}  

LRESULT
CALLBACK
MainUIThreadMessageHandler(HWND hwnd,
                           UINT uMsg,
                           WPARAM wParam,
                           LPARAM lParam)
{
    UNREFERENCED_PARAMETER(hwnd);
    UNREFERENCED_PARAMETER(wParam);
    UNREFERENCED_PARAMETER(lParam);
    HANDLE syncThread = INVALID_HANDLE_VALUE;

    //  Insert your initializations here.

    switch (uMsg) 
    {
        // User requested an operation on a file.  Insert your code to 
        // retrieve filename from parameters.
        case WM_MYSYNCOPS:    
            syncThread = CreateThread(
                          NULL,
                          0,
                          (LPTHREAD_START_ROUTINE)SynchronousIoWorker,
                          &g_lpFileName,
                          0,
                          NULL);

            if (syncThread == INVALID_HANDLE_VALUE) 
            {
                // Insert your code to handle the failure.
            }
        break;
    
        // User clicked a cancel button.
        case WM_MYCANCEL:
            if (syncThread != INVALID_HANDLE_VALUE) 
            {
                CancelSynchronousIo(syncThread);
            }
        break;

        // User requested other operations.
        case WM_PROCESSDATA:
            if (!g_dwOperationInProgress) 
            {
                if (g_CompletionStatus == ERROR_OPERATION_ABORTED) 
                {
                    // Insert your cleanup code here.
                } 
                else
                {
                    // Insert code to handle other cases.
                }
            }
        break;
    } 

    return TRUE;
} 

Synchrone und asynchrone E/A