取消挂起的 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 函数,则操作 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 操作的线程的句柄。

以下示例演示两个例程:

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

    注意

    在此示例中,UI 线程还可以检查工作线程的存在。

    同步IoWorker 函数中需要此处未包含的其他手动检查,以确保如果在文件 I/O 调用之间的短暂时间内请求取消,其余操作将被取消。

  • MainUIThreadMessageHandler 函数在 UI 线程的窗口过程中模拟消息处理程序。 用户通过单击一个控件来请求一组同步文件操作,该控件将生成用户定义的窗口消息, (WM_MYSYNCOPS) 标记 部分。 这会使用 CreateFileThread 函数创建新线程,然后启动 SynchronousIoWorker 函数。 当工作线程执行请求的 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