解除擱置的 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 時,請注意,每當執行緒在應用程式的不同部分之間共用時,都可以存在競爭條件,例如,使用執行緒集區執行緒。

無法取消的作業

某些函式無法使用CancelIo、CancelIoExCancelSynchronousIo函式取消。 其中一些函式已擴充為允許取消 (例如 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函式是實作一些同步檔案 I/O 的背景工作執行緒,從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