Annulation des opérations d’E/S en attente
Autoriser les utilisateurs à annuler des demandes d’E/S lentes ou bloquées peut améliorer la facilité d’utilisation et la robustesse de votre application. Par exemple, si un appel à la fonction OpenFile est bloqué parce que l’appel concerne un appareil très lent, l’annulation de l’appel permet de refaire l’appel, avec de nouveaux paramètres, sans mettre fin à l’application.
Windows Vista étend les fonctionnalités d’annulation et inclut la prise en charge de l’annulation des opérations synchrones.
Notes
L’appel de la fonction CancelIoEx ne garantit pas l’annulation d’une opération d’E/S . le pilote qui gère l’opération doit prendre en charge l’annulation et l’opération doit être dans un état qui peut être annulé.
Considérations relatives à l’annulation
Lorsque vous programmez des appels d’annulation, gardez à l’esprit les considérations suivantes :
- Il n’existe aucune garantie que les pilotes sous-jacents prennent correctement en charge l’annulation.
- Lors de l’annulation des E/S asynchrones, lorsqu’aucune structure chevauchée n’est fournie à la fonction CancelIoEx , la fonction tente d’annuler toutes les E/S en suspens sur le fichier sur tous les threads du processus. Chaque thread étant traité individuellement, une fois qu’un thread a été traité, il peut démarrer une autre E/S sur le fichier avant que tous les autres threads aient eu leur E/S pour le fichier annulé, ce qui entraîne des problèmes de synchronisation.
- Lors de l’annulation d’E/S asynchrones, ne réutilisez pas les structures qui se chevauchent avec une annulation ciblée. Une fois l’opération d’E/S terminée (avec succès ou avec un status annulé), la structure qui se chevauche n’est plus utilisée par le système et peut être réutilisée.
- Lors de l’annulation d’E/S synchrones, l’appel de la fonction CancelSynchronousIo tente d’annuler tout appel synchrone actuel sur le thread. Vous devez veiller à ce que la synchronisation des appels soit correcte ; un appel incorrect dans une série d’appels peut être annulé. Par exemple, si la fonction CancelSynchronousIo est appelée pour une opération synchrone, X, l’opération Y ne démarre qu’une fois cette opération X terminée (normalement ou avec une erreur). Si le thread qui a appelé l’opération X démarre ensuite un autre appel synchrone à X, l’appel d’annulation peut interrompre cette nouvelle demande d’E/S.
- Lors de l’annulation d’E/S synchrones, n’oubliez pas qu’une condition de race peut exister chaque fois qu’un thread est partagé entre différentes parties d’une application, par exemple avec un thread de pool de threads.
Opérations qui ne peuvent pas être annulées
Certaines fonctions ne peuvent pas être annulées à l’aide de la fonction CancelIo, CancelIoEx ou CancelSynchronousIo . Certaines de ces fonctions ont été étendues pour autoriser l’annulation (par exemple, la fonction CopyFileEx ) et vous devez les utiliser à la place. En plus de prendre en charge l’annulation, ces fonctions ont également des rappels intégrés pour vous aider lors du suivi de la progression de l’opération. Les fonctions suivantes ne prennent pas en charge l’annulation :
- CopyFile : utilisez CopyFileEx
- MoveFile : utilisez MoveFileWithProgress
- MoveFileEx : utilisez MoveFileWithProgress
- ReplaceFile
Pour plus d’informations, consultez Instructions relatives à l’achèvement/annulation des E/S.
Annulation d’E/S asynchrones
Vous pouvez annuler les E/S asynchrones à partir de n’importe quel thread du processus qui a émis l’opération d’E/S. Vous devez spécifier le handle sur lequel l’E/S a été effectuée et, éventuellement, la structure superposée utilisée pour effectuer les E/S. Vous pouvez déterminer si l’annulation s’est produite en examinant les status retournées dans la structure qui se chevauche ou dans le rappel d’achèvement. Une status de ERROR_OPERATION_ABORTED indique que l’opération a été annulée.
L’exemple suivant montre une routine qui prend un délai d’attente et tente une opération de lecture, l’annulant avec la fonction CancelIoEx si le délai d’expiration expire.
#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;
}
Annulation d’E/S synchrones
Vous pouvez annuler les E/S synchrones à partir de n’importe quel thread du processus qui a émis l’opération d’E/S. Vous devez spécifier le handle du thread qui effectue actuellement l’opération d’E/S.
L’exemple suivant montre deux routines :
La fonction SynchronousIoWorker est un thread de travail qui implémente des E/S de fichiers synchrones, en commençant par un appel à la fonction CreateFile . Si la routine réussit, la routine peut être suivie d’opérations supplémentaires, qui ne sont pas incluses ici. La variable globale gCompletionStatus peut être utilisée pour déterminer si toutes les opérations ont réussi ou si une opération a échoué ou a été annulée. La variable globale dwOperationInProgress indique si les E/S de fichier sont toujours en cours.
Notes
Dans cet exemple, le thread d’interface utilisateur peut également case activée pour l’existence du thread de travail.
Des vérifications manuelles supplémentaires, qui ne sont pas incluses ici, sont requises dans la fonction SynchronousIoWorker pour garantir que si l’annulation a été demandée pendant les brèves périodes entre les appels d’E/S de fichier, le reste des opérations sera annulé.
La fonction MainUIThreadMessageHandler simule le gestionnaire de messages dans la procédure de fenêtre d’un thread d’interface utilisateur. L’utilisateur demande un ensemble d’opérations de fichier synchrones en cliquant sur un contrôle qui génère un message de fenêtre défini par l’utilisateur (dans la section marquée par WM_MYSYNCOPS). Cela crée un thread à l’aide de la fonction CreateFileThread , qui démarre ensuite la fonction SynchronousIoWorker . Le thread d’interface utilisateur continue à traiter les messages pendant que le thread de travail effectue les E/S demandées. Si l’utilisateur décide d’annuler les opérations non terminées (généralement en cliquant sur un bouton Annuler), la routine (dans la section marquée par WM_MYCANCEL) appelle la fonction CancelSynchronousIo à l’aide du handle de thread retourné par la fonction CreateFileThread . La fonction CancelSynchronousIo retourne immédiatement après la tentative d’annulation. Enfin, l’utilisateur ou l’application peut demander ultérieurement une autre opération qui dépend de la fin ou non des opérations de fichier. Dans ce cas, la routine (dans la section marquée par WM_PROCESSDATA) vérifie d’abord que les opérations sont terminées, puis exécute les opérations propre-up.
Notes
Dans cet exemple, étant donné que l’annulation a pu se produire n’importe où dans la séquence d’opérations, il peut être nécessaire que l’appelant s’assure que l’état est cohérent, ou au moins compris, avant de continuer.
// 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;
}