2009-12-17 17 views
9

Ho bisogno di trovare un modo per essere avvisato quando un System.IO.Pipe.NamedPipeServerStream aperto in modalità asincrona ha più dati disponibili per la lettura su di esso- un WaitHandle sarebbe l'ideale. Non posso semplicemente usare BeginRead() per ottenere un handle di questo tipo perché è possibile che io possa essere segnalato da un altro thread che vuole scrivere sulla pipe, quindi devo rilasciare il blocco sulla pipe e aspettare che la scrittura sia completa, e NamedPipeServerStream non ha un metodo CancelAsync. Ho anche provato a chiamare BeginRead(), quindi chiamando la funzione win32 CancelIO sulla pipe se il thread viene segnalato, ma non penso che questa sia una soluzione ideale perché se CancelIO viene chiamato proprio mentre i dati arrivano e vengono elaborati, essere lasciato cadere - desidero ancora conservare questi dati, ma elaborarli in un secondo momento, dopo la scrittura. Sospetto che la funzione win32 PeekNamedPipe potrebbe essere utile, ma mi piacerebbe evitare di dover continuamente eseguire il polling di nuovi dati con esso.Named Pipes - Peeking asincrono

Nel caso likley che il testo di cui sopra è un po 'poco chiaro, qui è più o meno quello che mi piacerebbe essere in grado di fare ...

NamedPipeServerStream pipe; 
ManualResetEvent WriteFlag; 
//initialise pipe 
lock (pipe) 
{ 
    //I wish this method existed 
    WaitHandle NewDataHandle = pipe.GetDataAvailableWaithandle(); 
    Waithandle[] BreakConditions = new Waithandle[2]; 
    BreakConditions[0] = NewDataHandle; 
    BreakConditions[1] = WriteFlag; 
    int breakcode = WaitHandle.WaitAny(BreakConditions); 
    switch (breakcode) 
    { 
     case 0: 
      //do a read on the pipe 
      break; 
     case 1: 
      //break so that we release the lock on the pipe 
      break; 
    } 
} 

risposta

9

Ok, quindi ho appena estratto questo codice dal mio codice, spero di aver eliminato tutta la logica della domanda. L'idea è di provare una lettura a lunghezza zero con ReadFile e attendere sia lpOverlapped.EventHandle (attivato quando la lettura è completa) sia un WaitHandle impostato quando un altro thread vuole scrivere nella pipe. Se la lettura deve essere interrotta a causa di un thread di scrittura, utilizzare CancelIoEx per annullare la lettura a lunghezza zero.

NativeOverlapped lpOverlapped; 
ManualResetEvent DataReadyHandle = new ManualResetEvent(false); 
lpOverlapped.InternalHigh = IntPtr.Zero; 
lpOverlapped.InternalLow = IntPtr.Zero; 
lpOverlapped.OffsetHigh = 0; 
lpOverlapped.OffsetLow = 0; 
lpOverlapped.EventHandle = DataReadyHandle.SafeWaitHandle.DangerousGetHandle(); 
IntPtr x = Marshal.AllocHGlobal(1); //for some reason, ReadFile doesnt like passing NULL in as a buffer 
bool rval = ReadFile(SerialPipe.SafePipeHandle, x, 0, IntPtr.Zero, 
    ref lpOverlapped); 
int BreakCause; 
if (!rval) //operation is completing asynchronously 
{ 
    if (GetLastError() != 997) //ERROR_IO_PENDING, which is in fact good 
     throw new IOException(); 
    //So, we have a list of conditions we are waiting for 
    WaitHandle[] BreakConditions = new WaitHandle[3]; 
    //We might get some input to read from the serial port... 
    BreakConditions[0] = DataReadyHandle; 
    //we might get told to yield the lock so that CPU can write... 
    BreakConditions[1] = WriteRequiredSignal; 
    //or we might get told that this thread has become expendable 
    BreakConditions[2] = ThreadKillSignal; 
    BreakCause = WaitHandle.WaitAny(BreakConditions, timeout); 
} 
else //operation completed synchronously; there is data available 
{ 
    BreakCause = 0; //jump into the reading code in the switch below 
} 
switch (BreakCause) 
{ 
    case 0: 
     //serial port input 
     byte[] Buffer = new byte[AttemptReadSize]; 
     int BRead = SerialPipe.Read(Buffer, 0, AttemptReadSize); 
     //do something with your bytes. 
     break; 
    case 1: 
     //asked to yield 
     //first kill that read operation 
     CancelIoEx(SerialPipe.SafePipeHandle, ref lpOverlapped); 
     //should hand over the pipe mutex and wait to be told to tkae it back 
     System.Threading.Monitor.Exit(SerialPipeLock); 
     WriteRequiredSignal.Reset(); 
     WriteCompleteSignal.WaitOne(); 
     WriteCompleteSignal.Reset(); 
     System.Threading.Monitor.Enter(SerialPipeLock); 
     break; 
    case 2: 
     //asked to die 
     //we are the ones responsible for cleaning up the pipe 
     CancelIoEx(SerialPipe.SafePipeHandle, ref lpOverlapped); 
     //finally block will clean up the pipe and the mutex 
     return; //quit the thread 
} 
Marshal.FreeHGlobal(x); 
+0

+1: l'utilizzo di letture a 0 byte con I/O sovrapposti come metodo per ricevere un segnale quando i dati vengono scritti senza leggere effettivamente alcun dato è molto utile e non è reso evidente nella documentazione. –

1

Guardando attraverso MSDN, non vedo alcun meccanismo fare ciò che vuoi La soluzione più rapida è probabilmente quella di utilizzare l'interoperabilità per accedere a PeekNamedPipe. Se non si desidera utilizzare interop, è possibile astrarre la pipe all'interno di una classe personalizzata e fornire la funzionalità di visualizzazione all'interno dell'astrazione. L'astrazione gestirà tutta la segnalazione e dovrà coordinare la lettura e la scrittura sul tubo. Ovviamente, non è un compito banale.

Un'altra alternativa, se possibile nella tua situazione, è quella di esaminare l'utilizzo di WCF che è più o meno un'astrazione.

+0

Oh wow, una risposta! Grazie :) In realtà ho risolto questa età, però, sistemerò la soluzione ora. –