取消挂起的 I/O 操作

允许用户取消速度缓慢或被阻止的 I/O 请求可以提高应用程序的可用性和稳定性。 例如,如果阻止了 对 OpenFile 函数的调用,因为调用速度非常慢的设备,请取消它,使调用能够再次调用(使用新参数)而不终止应用程序。

Windows Vista 扩展了取消功能,并支持取消同步操作。

注意 调用 CancelIoEx 函数不能保证将取消 I/O 操作;正在处理操作的驱动程序必须支持取消,并且该操作必须处于可以取消的状态。

取消注意事项

编程取消调用时,请记住以下注意事项:

  • 不能保证基础驱动程序正确支持取消。
  • 取消异步 I/O 时,如果未向 CancelIoEx 函数提供重叠结构,该函数将尝试取消进程所有线程上文件上所有未完成的 I/O。 每个线程单独处理,因此在处理线程后,它可能会在文件上启动另一个 I/O,然后所有其他线程已取消文件 I/O,从而导致同步问题。
  • 取消异步 I/O 时,请勿将重叠结构与目标取消重复使用。 I/O 操作成功完成 (或已取消状态) 之后,系统不再使用重叠的结构,可以重复使用。
  • 取消同步 I/O 时,调用 CancelSynchronousIo 函数会尝试取消线程上的任何当前同步调用。 必须注意确保调用的同步正确;一系列呼叫中的错误呼叫可能会被取消。 例如,如果为同步操作调用 CancelSynchronousIo 函数,则 X、操作 Y 仅在操作 X 正常 (完成后启动,或者) 错误。 如果调用操作 X 的线程随后启动对 X 的另一个同步调用,则取消调用可能会中断此新的 I/O 请求。
  • 取消同步 I/O 时,请注意,每当线程在应用程序的不同部分之间共享线程(例如,使用线程池线程)时,就会存在争用条件。

无法取消的操作

某些函数无法使用 CancelIoCancelIoExCancelSynchronousIo 函数取消。 其中一些函数已扩展为允许取消 (例如 CopyFileEx 函数) ,应改用这些函数。 除了支持取消之外,这些函数还具有内置回调,用于在跟踪操作进度时支持你。 以下函数不支持取消:

有关详细信息,请参阅 I/O 完成/取消指南

取消异步 I/O

可以从发出 I/O 操作的进程中的任何线程取消异步 I/O。 必须指定在其中执行 I/O 的句柄,并且(可选)用于执行 I/O 的重叠结构。 可以通过检查重叠结构或完成回调中返回的状态来确定是否发生了取消。 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;
}

取消同步 I/O

可以从发出 I/O 操作的进程中的任何线程取消同步 I/O。 必须指定当前正在执行 I/O 操作的线程的句柄。

以下示例演示两个例程:

  • SyncIoWorker 函数是一个工作线程,它实现一些同步文件 I/O,从对 CreateFile 函数的调用开始。 如果例程成功,则例程后跟其他操作,此处未包含这些操作。 全局变量 gCompletionStatus 可用于确定所有操作是否成功,还是操作失败或已取消。 全局变量 dwOperationInProgress 指示文件 I/O 是否仍在进行中。

    注意 在此示例中,UI 线程还可以检查工作线程是否存在。

    SyncIoWorker 函数中不需要进行其他手动检查,以确保如果在文件 I/O 调用之间的短暂期间请求取消,将取消其余操作。

  • MainUIThreadMessageHandler 函数模拟 UI 线程窗口过程中的消息处理程序。 用户通过单击生成用户定义的窗口消息的控件来请求一组同步文件操作, (由 WM_MYSYNCOPS) 标记的部分。 这会使用 CreateFileThread 函数创建新线程,然后启动 SyncIoWorker 函数。 当工作线程执行请求的 I/O 时,UI 线程会继续处理消息。 如果用户决定通过单击取消按钮 () 来取消未完成的操作,WM_MYCANCEL标记的节中的例程 () 使用CreateFileThread 函数返回的线程句柄调用 CancelSynchronousIo 函数。 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;
} 

同步和异步 I/O