保留中の 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 関数を呼び出すと、スレッドの現在の同期呼び出しが取り消されます。 呼び出しの同期が正しいことを確認する必要があります。一連の呼び出しで間違った呼び出しが取り消される可能性があります。 たとえば、同期操作 X に対して CancelSynchronousIo 関数が呼び出された場合、操作 Y は、その操作 X が完了した後 (通常またはエラーが発生) 後にのみ開始されます。 操作 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 操作を実行しているスレッドへのハンドルを指定する必要があります。

次の例は、2 つのルーチンを示しています。

  • 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