共用方式為


取消擱置的 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 作業的線程的控制代碼。

下列範例顯示兩個例程:

  • SynchronousIoWorker 函式代表實作一些同步檔案輸入輸出的工作緒,從呼叫 CreateFile 函式開始。 如果例程成功,則例程可以接著其他作業,此處未包含這些作業。 全域變數 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