Anulowanie oczekujących operacji we/wy
Zezwolenie użytkownikom na anulowanie żądań we/wy, które są powolne lub zablokowane, może zwiększyć użyteczność i niezawodność aplikacji. Jeśli na przykład wywołanie funkcji OpenFile jest zablokowane, ponieważ jest kierowane do bardzo wolnego urządzenia, to anulowanie tego wywołania umożliwia jego ponowne wykonanie z nowymi parametrami, bez przerywania działania aplikacji.
System Windows Vista rozszerza możliwości anulowania i obejmuje obsługę anulowania operacji synchronicznych.
Notatka
Wywołanie funkcji CancelIoEx nie gwarantuje, że operacja we/wy zostanie anulowana; sterownik obsługujący operację musi obsługiwać anulowanie, a operacja musi być w stanie, który można anulować.
Zagadnienia dotyczące anulowania
Podczas programowania wywołań anulowania należy pamiętać o następujących kwestiach:
- Nie ma gwarancji, że bazowe sterowniki prawidłowo obsługują anulowanie.
- W przypadku anulowania asynchronicznej operacji we/wy, gdy do funkcji CancelIoEx nie jest dostarczana żadna struktura nakładająca się, funkcja próbuje anulować wszystkie asynchroniczne operacje we/wy w pliku na wszystkich wątkach w procesie. Każdy wątek jest przetwarzany indywidualnie, więc po przetworzeniu może rozpocząć kolejną operację wejścia-wyjścia na pliku, zanim wszystkie inne wątki zakończą swoje operacje wejścia-wyjścia lub zostaną one anulowane, co może powodować problemy z synchronizacją.
- W przypadku anulowania asynchronicznych operacji we/wy nie należy ponownie używać nakładających się struktur z docelowym anulowaniem. Po zakończeniu operacji we/wy (pomyślnie lub z anulowanym statusem) struktura nakładkowa nie jest już używana przez system i może zostać ponownie użyta.
- Podczas anulowania operacji synchronicznego wejścia/wyjścia wywołanie funkcji CancelSynchronousIo próbuje anulować bieżące synchroniczne wywołanie w wątku. Należy zadbać o to, aby upewnić się, że synchronizacja wywołań jest poprawna; nieprawidłowe wywołanie w serii wywołań może zostać anulowane. Jeśli na przykład funkcja CancelSynchronousIo jest wywoływana dla operacji synchronicznej, X, operacja Y jest uruchamiana tylko po zakończeniu tej operacji X (zwykle lub z błędem). Jeśli wątek, który wykonuje operację X, następnie uruchamia kolejne synchroniczne wywołanie do X, wywołanie anulowania może przerwać to nowe żądanie I/O.
- Podczas anulowania synchronicznych operacji we/wy należy pamiętać, że warunek wyścigu może wystąpić za każdym razem, gdy wątek jest współużytkowany między różnymi częściami aplikacji, na przykład z wątkiem z puli wątków.
Operacje, których nie można anulować
Niektórych funkcji nie można anulować przy użyciu funkcji CancelIo, CancelIoExlub CancelSynchronousIo. Niektóre z tych funkcji zostały rozszerzone w celu umożliwienia anulowania (na przykład funkcji CopyFileEx) i należy ich użyć. Oprócz obsługi anulowania te funkcje mają również wbudowane wywołania zwrotne, aby wspomóc cię podczas śledzenia postępu operacji. Następujące funkcje nie obsługują anulowania:
- CopyFile— użyj CopyFileEx
- MoveFile– użyj MoveFileWithProgress
- MoveFileEx— użyj MoveFileWithProgress
- ZamieńPlik
Aby uzyskać więcej informacji, zobacz Wskazówki dotyczące realizacji/anulowania operacji wejścia/wyjścia.
Anulowanie asynchronicznego we/wy
Możesz anulować asynchroniczne operacje I/O z dowolnego wątku w procesie, który zainicjował operację I/O. Należy określić uchwyt, na którym wykonano operacje we/wy, i opcjonalnie nakładającą się strukturę, która została użyta do wykonania operacji we/wy. Możesz określić, czy anulowanie wystąpiło, sprawdzając stan zwrócony w nakładającej się strukturze lub w wywołaniu zwrotnym ukończenia. Stan ERROR_OPERATION_ABORTED wskazuje, że operacja została anulowana.
W poniższym przykładzie przedstawiono rutynę, która przyjmuje limit czasu i próbuje wykonać operację odczytu, anulując ją za pomocą funkcji CancelIoEx, jeżeli limit czasu zostanie przekroczony.
#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;
}
Anulowanie synchronicznego wejścia/wyjścia
Możesz anulować synchroniczne we/wy z dowolnego wątku w procesie, który wystawił operację we/wy. Należy określić uchwyt do wątku, który obecnie wykonuje operację I/O.
W poniższym przykładzie przedstawiono dwie procedury:
Funkcja SynchronousIoWorker to wątek procesu roboczego, który implementuje niektóre synchroniczne operacje we/wy pliku, począwszy od wywołania funkcji CreateFile. Jeśli rutynowa operacja zakończy się pomyślnie, można przeprowadzić dodatkowe operacje, które nie zostały tutaj uwzględnione. Zmienna globalna gCompletionStatus może służyć do określenia, czy wszystkie operacje zakończyły się pomyślnie, czy operacja zakończyła się niepowodzeniem, czy została anulowana. Zmienna globalna dwOperationInProgress wskazuje, czy operacje wejścia/wyjścia pliku są w toku.
Notatka
W tym przykładzie wątek interfejsu użytkownika może także sprawdzić, czy wątek roboczy istnieje.
Dodatkowe testy ręczne, które nie są uwzględnione w tym miejscu, są wymagane w funkcji SynchronousIoWorker, aby upewnić się, że jeśli anulowanie zostało żądane w krótkich okresach między wywołaniami I/O dla plików, pozostałe operacje będą anulowane.
Funkcja MainUIThreadMessageHandler symuluje program obsługi komunikatów w procedurze okna wątku interfejsu użytkownika. Użytkownik żąda zestawu synchronicznych operacji na plikach, klikając kontrolkę, która generuje komunikat okna zdefiniowany przez użytkownika (w sekcji oznaczonej przez WM_MYSYNCOPS). Spowoduje to utworzenie nowego wątku przy użyciu funkcji CreateFileThread, która następnie uruchamia funkcję SynchronousIoWorker. Wątek interfejsu użytkownika nadal przetwarza komunikaty, podczas gdy wątek procesu roboczego wykonuje żądane operacje we/wy. Jeśli użytkownik zdecyduje się anulować niedokończone operacje (zazwyczaj klikając przycisk anulowania), procedura (w sekcji oznaczonej przez WM_MYCANCEL) wywołuje funkcję CancelSynchronousIo, używając dojścia wątku zwróconego przez funkcję CreateFileThread. Funkcja CancelSynchronousIo jest zwracana natychmiast po próbie anulowania. Na koniec użytkownik lub aplikacja może później zażądać innego działania, które zależy od tego, czy operacje na plikach zostały ukończone. W takim przypadku rutyna (w sekcji oznaczonej przez WM_PROCESSDATA) najpierw sprawdza ukończenie operacji, a następnie wykonuje operacje sprzątające.
Notatka
W tym przykładzie, ponieważ anulowanie mogło wystąpić w dowolnym miejscu w sekwencji operacji, może być konieczne, aby obiekt wywołujący zapewnił, że stan jest spójny lub przynajmniej zrozumiały przed kontynuowaniem.
// 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;
}