Именованные каналы — асинхронный просмотр

Мне нужно найти способ получать уведомления, когда System.IO.Pipe.NamedPipeServerStream, открытый в асинхронном режиме, имеет больше данных, доступных для чтения, — WaitHandle был бы идеальным. Я не могу просто использовать BeginRead() для получения такого дескриптора, потому что возможно, что мне может быть сообщен другой поток, который хочет записать в канал, поэтому я должен снять блокировку канала и дождаться завершения записи, и NamedPipeServerStream не имеет метода CancelAsync. Я также пробовал вызывать BeginRead(), а затем вызывать функцию win32 CancelIO в канале, если поток получает сигнал, но я не думаю, что это идеальное решение, потому что если CancelIO вызывается сразу после поступления и обработки данных, он быть отброшенным - я все еще хочу сохранить эти данные, но обработаю их позже, после записи. Я подозреваю, что функция Win32 PeekNamedPipe может быть полезна, но я бы хотел избежать постоянного опроса новых данных с ее помощью.

В том случае, если приведенный выше текст немного неясен, вот примерно то, что я хотел бы сделать...

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;
     }
}

person KJ Tsanaktsidis    schedule 17.12.2009    source источник


Ответы (2)


Итак, я просто вырвал это из своего кода, надеюсь, я удалил всю логику приложения. Идея состоит в том, что вы пытаетесь выполнить чтение с нулевой длиной с помощью ReadFile и ждете как lpOverlapped.EventHandle (запускается после завершения чтения), так и набора WaitHandle, когда другой поток хочет записать в канал. Если чтение должно быть прервано из-за потока записи, используйте CancelIoEx для отмены чтения нулевой длины.

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);
person KJ Tsanaktsidis    schedule 21.03.2010
comment
+1: использование чтения 0-байтов с перекрывающимся вводом-выводом в качестве способа получения сигнала, когда данные записываются без фактического чтения каких-либо данных, очень полезно и не указано в документации. - person Glenn Maynard; 19.09.2010

Просматривая MSDN, я не вижу никакого механизма, чтобы делать то, что вы хотите. Возможно, самое быстрое решение — использовать взаимодействие для доступа к PeekNamedPipe. Если вы не хотите использовать interop, вы можете абстрагировать канал внутри пользовательского класса и предоставить функциональность просмотра внутри абстракции. Абстракция будет обрабатывать всю сигнализацию и должна координировать чтение и запись в канал. Понятно, что нетривиальная задача.

Другой альтернативой, если это возможно в вашей ситуации, является изучение использования WCF, который в значительной степени является абстракцией.

person Scott J    schedule 19.03.2010
comment
Ого, ответ! Спасибо :) На самом деле я решил это давным-давно, сейчас я выложу решение. - person KJ Tsanaktsidis; 21.03.2010