取消擱置的 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、CancelIoEx或 CancelSynchronousIo 函式來取消。 部分函式已擴充為允許取消(例如,CopyFileEx 函式),您應該改用這些函式。 除了支援取消之外,這些函式還有內建的回呼函式,可以在您追蹤作業進度時提供支援。 下列函式不支援取消:
- CopyFile— 使用 CopyFileEx
- MoveFile— 使用 MoveFileWithProgress
- MoveFileEx— 使用 MoveFileWithProgress
- 替換檔案
如需詳細資訊,請參閱 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;
}