Отмена ожидающих операций ввода-вывода

Разрешение пользователям отменять медленные или заблокированные запросы ввода-вывода может повысить удобство использования и надежность приложения. Например, если вызов функции OpenFile блокируется из-за того, что он выполняется на очень медленном устройстве, отмена этого вызова позволяет снова выполнить вызов с новыми параметрами, не завершая работу приложения.

Windows Vista расширяет возможности отмены и включает поддержку отмены синхронных операций.

Примечание

Вызов функции CancelIoEx не гарантирует отмену операции ввода-вывода. драйвер, обрабатывающий операцию, должен поддерживать отмену, и операция должна находиться в состоянии, которое может быть отменено.

Рекомендации по отмене

При программировании вызовов отмены учитывайте следующие моменты:

  • Нет никакой гарантии, что базовые драйверы правильно поддерживают отмену.
  • При отмене асинхронного ввода-вывода, когда функция CancelIoEx не предоставляет перекрывающуюся структуру, функция пытается отменить все незавершенные операции ввода-вывода в файле во всех потоках процесса. Каждый поток обрабатывается по отдельности, поэтому после обработки потока он может запустить еще один ввод-вывод в файле до того, как все остальные потоки отменят ввод-вывод для файла, что приведет к проблемам синхронизации.
  • При отмене асинхронного ввода-вывода не следует повторно использовать перекрывающиеся структуры с целевой отменой. После завершения операции ввода-вывода (успешно или с состоянием отмены) перекрываемая структура больше не используется системой и может использоваться повторно.
  • При отмене синхронного ввода-вывода вызов функции CancelSynchronousIo пытается отменить любой текущий синхронный вызов в потоке. Необходимо позаботиться о правильности синхронизации вызовов; Неправильный вызов в серии вызовов может быть отменен. Например, если функция CancelSynchronousIo вызывается для синхронной операции X, операция Y запускается только после завершения этой операции X (обычно или с ошибкой). Если поток, который вызвал операцию X, затем запускает другой синхронный вызов X, вызов отмены может прервать этот новый запрос ввода-вывода.
  • При отмене синхронного ввода-вывода имейте в виду, что состояние гонки может существовать, когда поток совместно используется разными частями приложения, например потоком пула потоков.

Операции, которые не могут быть отменены

Некоторые функции нельзя отменить с помощью функции CancelIo, CancelIoEx или CancelSynchronousIo . Некоторые из этих функций были расширены, чтобы разрешить отмену (например, функция CopyFileEx ), и их следует использовать. Помимо поддержки отмены, эти функции также имеют встроенные обратные вызовы для отслеживания хода выполнения операции. Следующие функции не поддерживают отмену:

Дополнительные сведения см. в разделе Рекомендации по завершению и отмене ввода-вывода.

Отмена асинхронного ввода-вывода

Вы можете отменить асинхронный ввод-вывод из любого потока в процессе, выпустившего операцию ввода-вывода. Необходимо указать дескриптор, с которым был выполнен ввод-вывод, и, при необходимости, перекрывающуюся структуру, которая использовалась для выполнения операций ввода-вывода. Чтобы определить, произошла ли отмена, проверьте состояние, возвращенное в перекрывающейся структуре или в обратном вызове завершения. Состояние ERROR_OPERATION_ABORTED указывает, что операция была отменена.

В следующем примере показана подпрограмма, которая принимает время ожидания и пытается выполнить операцию чтения, отменяя ее с помощью функции CancelIoEx , если истекло время ожидания.

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

Отмена синхронного ввода-вывода

Синхронный ввод-вывод можно отменить из любого потока в процессе, выдавшего операцию ввода-вывода. Необходимо указать дескриптор для потока, который в настоящее время выполняет операцию ввода-вывода.

В следующем примере показаны две подпрограммы:

  • Функция SynchronousIoWorker — это рабочий поток, который реализует некоторые синхронные операции ввода-вывода файлов, начиная с вызова функции CreateFile . Если подпрограмма выполнена успешно, за ней могут следовать дополнительные операции, которые здесь не включены. Глобальную переменную gCompletionStatus можно использовать для определения успешности всех операций, сбоя или отмены операции. Глобальная переменная dwOperationInProgress указывает, выполняется ли файловый ввод-вывод.

    Примечание

    В этом примере поток пользовательского интерфейса также может проверка для существования рабочего потока.

    В функции SynchronousIoWorker требуются дополнительные проверки вручную, которые не включены в функцию SynchronousIoWorker , чтобы убедиться, что если отмена была запрошена в течение коротких периодов между вызовами файлового ввода-вывода, остальные операции будут отменены.

  • Функция MainUIThreadMessageHandler имитирует обработчик сообщений в процедуре окна потока пользовательского интерфейса. Пользователь запрашивает набор синхронных операций с файлами, щелкнув элемент управления , который создает сообщение о определяемом пользователем окне (в разделе, отмеченном WM_MYSYNCOPS). При этом создается новый поток с помощью функции CreateFileThread , которая затем запускает функцию SynchronousIoWorker . Поток пользовательского интерфейса продолжает обрабатывать сообщения, а рабочий поток выполняет запрошенные операции ввода-вывода. Если пользователь решает отменить незавершенные операции (обычно нажимая кнопку отмены), подпрограмма (в разделе, отмеченном WM_MYCANCEL) вызывает функцию CancelSynchronousIo с помощью дескриптора потока, возвращаемого функцией CreateFileThread . Функция CancelSynchronousIo возвращается сразу после попытки отмены. Наконец, пользователь или приложение может позже запросить другую операцию, которая зависит от того, были ли завершены операции с файлами. В этом случае подпрограмма (в разделе, отмеченном WM_PROCESSDATA) сначала проверяет завершение операций, а затем выполняет операции очистки.

    Примечание

    В этом примере, так как отмена могла произойти в любом месте последовательности операций, вызывающему объекту может потребоваться убедиться, что состояние является согласованным или по крайней мере понятным, прежде чем продолжить.

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

Синхронный и асинхронный ввод-вывод