Share via


Cancelación de operaciones de E/S pendientes

Permitir a los usuarios cancelar solicitudes de E/S lentas o bloqueadas puede mejorar la facilidad de uso y la solidez de la aplicación. Por ejemplo, si se bloquea una llamada a la función OpenFile porque la llamada es a un dispositivo muy lento, al cancelarla, se vuelve a realizar la llamada, con nuevos parámetros, sin terminar la aplicación.

Windows Vista amplía las funcionalidades de cancelación e incluye compatibilidad para cancelar operaciones sincrónicas.

Nota

Llamar a la función CancelIoEx no garantiza que se cancele una operación de E/S; el controlador que controla la operación debe admitir la cancelación y la operación debe estar en un estado que se pueda cancelar.

Consideraciones sobre cancelación

Al programar llamadas de cancelación, tenga en cuenta las consideraciones siguientes:

  • No hay ninguna garantía de que los controladores subyacentes admitan correctamente la cancelación.
  • Al cancelar la E/S asincrónica, cuando no se proporciona ninguna estructura superpuesta a la función CancelIoEx , la función intenta cancelar todas las E/S pendientes del archivo en todos los subprocesos del proceso. Cada subproceso se procesa individualmente, por lo que después de procesar un subproceso, puede iniciar otra E/S en el archivo antes de que todos los demás subprocesos hayan tenido su E/S para el archivo cancelado, lo que provoca problemas de sincronización.
  • Al cancelar E/S asincrónica, no reutilice estructuras superpuestas con cancelación de destino. Una vez completada la operación de E/S (ya sea correctamente o con un estado cancelado), la estructura superpuesta ya no está en uso por el sistema y se puede reutilizar.
  • Al cancelar la E/S sincrónica, al llamar a la función CancelSynchronousIo se intenta cancelar cualquier llamada sincrónica actual en el subproceso. Debe tener cuidado para asegurarse de que la sincronización de las llamadas es correcta; la llamada incorrecta en una serie de llamadas podría cancelarse. Por ejemplo, si se llama a la función CancelSynchronousIo para una operación sincrónica, X, la operación Y solo se inicia después de que se complete esa operación (normalmente o con un error). Si el subproceso que llamó a la operación X, inicia otra llamada sincrónica a X, la llamada de cancelación podría interrumpir esta nueva solicitud de E/S.
  • Al cancelar la E/S sincrónica, tenga en cuenta que una condición de carrera puede existir cada vez que un subproceso se comparte entre diferentes partes de una aplicación, por ejemplo, con un subproceso del grupo de subprocesos.

Operaciones que no se pueden cancelar

Algunas funciones no se pueden cancelar mediante la función CancelIo, CancelIoEx o CancelSynchronousIo . Algunas de estas funciones se han ampliado para permitir la cancelación (por ejemplo, la función CopyFileEx ) y debe usarlas en su lugar. Además de admitir la cancelación, estas funciones también tienen devoluciones de llamada integradas para admitir el seguimiento del progreso de la operación. Las siguientes funciones no admiten la cancelación:

Para obtener más información, consulte Instrucciones de finalización o cancelación de E/S.

Cancelación de E/S asincrónica

Puede cancelar la E/S asincrónica desde cualquier subproceso del proceso que emitió la operación de E/S. Debe especificar el identificador en el que se realizó la E/S y, opcionalmente, la estructura superpuesta que se usó para realizar la E/S. Puede determinar si se produjo la cancelación examinando el estado devuelto en la estructura superpuesta o en la devolución de llamada de finalización. Un estado de ERROR_OPERATION_ABORTED indica que se canceló la operación.

En el ejemplo siguiente se muestra una rutina que tarda un tiempo de espera e intenta una operación de lectura, cancelándola con la función CancelIoEx si expira el tiempo de espera.

#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;
}

Cancelación de E/S sincrónica

Puede cancelar la E/S sincrónica desde cualquier subproceso del proceso que emitió la operación de E/S. Debe especificar el identificador para el subproceso que está realizando actualmente la operación de E/S.

En el ejemplo siguiente se muestran dos rutinas:

  • La función SynchronousIoWorker es un subproceso de trabajo que implementa alguna E/S de archivo sincrónica, empezando por una llamada a la función CreateFile . Si la rutina se realiza correctamente, la rutina puede seguir las operaciones adicionales, que no se incluyen aquí. La variable global gCompletionStatus se puede usar para determinar si todas las operaciones se realizaron correctamente o si se produjo un error en una operación o se canceló. La variable global dwOperationInProgress indica si la E/S del archivo todavía está en curso.

    Nota

    En este ejemplo, el subproceso de interfaz de usuario también podría comprobar la existencia del subproceso de trabajo.

    Las comprobaciones manuales adicionales, que no se incluyen aquí, son necesarias en la función SynchronousIoWorker es asegurarse de que si la cancelación se solicitó durante los breves períodos entre las llamadas de E/S de archivo, se cancelará el resto de las operaciones.

  • La función MainUIThreadMessageHandler simula el controlador de mensajes dentro del procedimiento de ventana de un subproceso de interfaz de usuario. El usuario solicita un conjunto de operaciones de archivo sincrónicas haciendo clic en un control, que genera un mensaje de ventana definido por el usuario (en la sección marcada por WM_MYSYNCOPS). Esto crea un nuevo subproceso mediante la función CreateFileThread , que luego inicia la función SynchronousIoWorker . El subproceso de interfaz de usuario continúa procesando mensajes mientras el subproceso de trabajo realiza la E/S solicitada. Si el usuario decide cancelar las operaciones sin terminar (normalmente haciendo clic en un botón cancelar), la rutina (en la sección marcada por WM_MYCANCEL) llama a la función CancelSynchronousIo mediante el identificador de subproceso devuelto por la función CreateFileThread . La función CancelSynchronousIo devuelve inmediatamente después del intento de cancelación. Por último, el usuario o la aplicación pueden solicitar posteriormente alguna otra operación que dependa de si las operaciones de archivo se han completado. En este caso, la rutina (en la sección marcada por WM_PROCESSDATA) comprueba primero que las operaciones se han completado y, a continuación, ejecuta las operaciones de limpieza.

    Nota

    En este ejemplo, dado que la cancelación podría haberse producido en cualquier lugar de la secuencia de operaciones, puede ser necesario que el autor de la llamada asegúrese de que el estado sea coherente o, al menos, comprendido, antes de continuar.

// 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;
} 

E/S sincrónica y asincrónica