보류 중인 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에 대해 호출되는 경우 해당 작업 X가 정상적으로 또는 오류가 발생하여 완료되면 작업 Y만 시작됩니다. 그런 다음, 작업 X를 호출한 스레드가 X에 대해 또 다른 동기 호출을 시작하면 취소 호출이 이 새 I/O 요청을 중단시킬 수 있습니다.
  • 동기 I/O를 취소하면 스레드가 스레드 풀 스레드와 같은 애플리케이션의 여러 부분 간에 공유될 때마다 경합 상태가 있을 수 있습니다.

취소할 수 없는 작업

일부 함수는 CancelIo, CancelIoEx 또는 CancelSynchronousIo 함수를 사용하여 취소할 수 없습니다. 이러한 함수 중 일부는 취소를 허용하도록 확장(예: 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 스레드는 작업자 스레드의 존재에 대해 검사 수 있습니다.

    여기에 포함되지 않은 추가 수동 검사는 SynchronousIoWorker 함수에서 파일 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