解除擱置的 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
- ReplaceFile
如需詳細資訊,請參閱 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;
}