Come sapere quando ReadFileEx() si è sovrapposto all'I/O è stato completato?
-
28-10-2019 - |
Domanda
HasOverlappedIoCompleted()
non funziona sugli I/O asincroni iniziati con ReadFileEx()
E WriteFileEx()
.Lo snippet di codice in basso lo dimostra.In questo esempio, ReadFileEx()
legge da una pipe che non ha input, quindi la lettura non verrà completata.Ma HasOverlappedIoCompleted()
restituisce VERO.Se cambio la chiamata in sovrapposta ReadFile()
Poi HasOverlappedIoCompleted()
restituisce FALSE come previsto.
La mia domanda è:Come posso scoprire se una richiesta di I/O sovrapposta con callback è stata completata, senza fare affidamento sulla callback stessa?Nella mia applicazione, l'APC potrebbe essere stato messo in coda ma non è necessariamente necessario che sia già stato eseguito perché l'applicazione potrebbe non essere ancora in attesa in uno stato di avviso.
Grazie.
(Nota GetOverlappedResult() non aiuta: restituisce anche TRUE.)
Un po' più di background:Nell'esempio che sto usando ReadFileEx()
perché è facile dimostrare il problema.Nella mia domanda sto chiamando WriteFileEx()
ripetutamente su un'istanza di pipe.Se il precedente WriteFileEx()
non ha ancora completato I dovere rilasciare il messaggio anziché inviarlo (non devo avere più di una scrittura in sospeso sulla stessa istanza della pipe), ma se il precedente WriteFileEx()
ha completato poi I dovere inizia il prossimo, anche se la richiamata di completamento non è ancora stata eseguita.
Modificare:Una descrizione dello scenario problematico
- Il thread entra in uno stato di avviso (con un APC di lettura in coda).
- Inizia la lettura dell'APC:Mette in coda un WriteFileEx() e imposta un flag di "scrittura in sospeso".Quindi mette in coda un ReadFileEx().
- Il thread principale inizia a funzionare (non segnalabile).
- La lettura in coda viene completata.
- La scrittura in coda viene completata (dopo la lettura).
- Il thread principale entra in uno stato di avviso.
- L'APC di lettura è il primo nella coda, quindi viene eseguito per primo:Controlla il flag "scrittura in sospeso" e poiché è ancora impostato elimina la scrittura.In effetti, sebbene WriteFileEx() ha completato, semplicemente non ha ancora chiamato il suo APC perché ReadFileEx() è stato completato per primo.
Invece di testare il mio flag personalizzato di "scrittura in sospeso", voglio scoprire dal sistema operativo se WriteFileEx() è stato effettivamente completato, anche se l'APC non è ancora stato eseguito.
#include <Windows.h>
#include <stdio.h>
#include <assert.h>
VOID CALLBACK readComplete(DWORD err, DWORD bytes, LPOVERLAPPED ovlp)
{
}
int main(int argc, char *argv[])
{
HANDLE hServer;
OVERLAPPED serverOvlp = { 0 };
HANDLE hClient;
DWORD bytes;
BYTE buffer[16];
BOOL result;
hServer = CreateNamedPipe("\\\\.\\pipe\\testpipe", PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES, 0, 0, 5000, NULL);
serverOvlp.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
ConnectNamedPipe(hServer, &serverOvlp);
assert(GetLastError() == ERROR_IO_PENDING);
hClient = CreateFile("\\\\.\\pipe\\testpipe", GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING, 0, NULL);
GetOverlappedResult(hServer, &serverOvlp, &bytes, TRUE);
/* Server starts an overlapped read */
// result = ReadFile(hServer, buffer, sizeof(buffer), &bytes, &serverOvlp);
result = ReadFileEx(hServer, buffer, sizeof(buffer), &serverOvlp, readComplete);
if (HasOverlappedIoCompleted(&serverOvlp))
{
puts("Completed");
}
else
{
puts("Not completed");
}
return EXIT_SUCCESS;
}
Soluzione
Il comportamento che chiedi mi sembra sbagliato, perché implica una race condition.Il problema si verifica solo se ricevi un messaggio A mentre stai ancora inviando un messaggio B.Attualmente A viene sempre ignorato, cioè non viene inviato alcun messaggio aggiuntivo.Il comportamento che stai tentando di ottenere comporterebbe l'invio di un messaggio aggiuntivo se e solo se il server era occupato nell'elaborazione del lavoro durante l'intervallo tra l'arrivo di A e il completamento di B.Penso che dovresti ignorare sempre A o inviare sempre una risposta una volta completato B.
Tuttavia, puoi ottenere questo comportamento se sei sicuro che sia quello che desideri.Una soluzione potrebbe essere quella di chiamare QueueUserAPC dalla routine di completamento ReadFileEx, per accodare una chiamata a una funzione che invia la risposta.Poiché gli APC vengono eseguiti in ordine FIFO, il nuovo APC verrà sicuramente eseguito dopo l'APC che WriteFileEx ha già messo in coda.
A seconda dei dettagli, potrebbe essere più pulito per entrambe le routine di completamento impostare flag o aggiungere elementi a una coda e fare in modo che il ciclo principale svolga il lavoro vero e proprio.
Se è necessario eseguire il polling per gli APC (ad esempio perché il ciclo principale non prevede operazioni di attesa naturali) è possibile utilizzare WaitForSingleObjectEx su un evento fittizio con un timeout pari a 0.Non importa se l'evento viene segnalato o meno, verranno comunque chiamati tutti gli APC in coda.
Altri suggerimenti
Ho modificato leggermente il tuo codice per ottenere il CALLBACK.Affinché questo esempio funzioni, la lunghezza del buffer della pipe non deve essere 0.Inoltre, penso che entrambe le estremità della pipe dovrebbero avere il flag OVERLAPPED impostato.
#include <Windows.h>
#include <stdio.h>
#include <assert.h>
#define BUFFSIZE 100
#define MYPIPE "\\\\.\\pipe\\testpipe"
typedef struct {
OVERLAPPED serverOvlp; // Overlapped should always be first in structure
CHAR buffer[20];
} OVLP;
VOID CALLBACK readComplete(DWORD err, DWORD bytes, LPOVERLAPPED ovlp)
{
OVLP *temp = (OVLP *) ovlp;
printf("readComplete err=%d bytes=%d buffer=%s\n", err, bytes, temp->buffer);
}
int main(void)
{
HANDLE hServer;
HANDLE hClient;
OVLP oServer;
DWORD bytes;
CHAR ClientBuffer[20] = "Test message";
hServer = CreateNamedPipe(MYPIPE, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES, BUFFSIZE, BUFFSIZE, 5000, NULL);
//-------------------------------------- CLIENT
hClient = CreateFile(MYPIPE, GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
WriteFile(hClient,ClientBuffer,strlen(ClientBuffer)+1,&bytes,NULL);
//-------------------------------------- SERVER
ConnectNamedPipe(hServer, &oServer.serverOvlp);
if (HasOverlappedIoCompleted(&oServer.serverOvlp)) assert(GetLastError() != 0 );
puts("Client Pipe connected\n");
ReadFileEx(hServer, oServer.buffer, sizeof(oServer.buffer), (LPOVERLAPPED)&oServer, readComplete);
SleepEx(INFINITE,TRUE); // Creates an alertable event so CALLBACK is triggered
if (HasOverlappedIoCompleted(&oServer.serverOvlp)) {
puts("Completed");
} else {
puts("Not completed");
}
return EXIT_SUCCESS;
}