C # Concurrencia: enfoque preferido para tareas de ejecución prolongada

Cuando es necesario que haya una escucha de E / S ejecutándose durante el tiempo de vida de una aplicación, ¿qué modelo de concurrencia se prefiere con C # 5.0 ejecutándose en el marco 4.5?

Me he conformado con la idea de que un patrón de productor-consumidor sería el mejor para manejar lo que recibo, pero ¿qué infraestructura debería soportarlo?

¿Se recomendaría un Thread thread = new Thread(ThreadStart(method)) simple Thread thread = new Thread(ThreadStart(method)) ? ¿O tal vez sería preferible un modelo Task o Async/Await ?

Es una pregunta rápida, pero como estoy al principio del diseño, prefiero asegurarme de que la base sea sólida. Mi reacción visceral proveniente de otros idiomas es que un hilo simple que se ejecuta en segundo plano es el mejor, pero la plétora de marcos paralelos en C # me ha desviado del rumbo.

Si fuera relevante, el tiempo de sondeo en mi aplicación se manejaría por el tiempo de espera de lectura de E / S.

Actualizar:

La I / OI que se refiere a es un dispositivo FTDI, donde el dispositivo puede ingresar bytes a la PC en cualquier momento, dependiendo del estado del controlador de a bordo. Como resultado, siempre necesito estar listo para recoger los datos y procesarlos. La API que estoy usando se basa en la DLL D2XX proporcionada por el proveedor de FTDI.

La I / OI que se refiere a es un dispositivo FTDI, donde el dispositivo puede ingresar bytes a la PC en cualquier momento, dependiendo del estado del controlador de a bordo. Como resultado, siempre necesito estar listo para recoger los datos y procesarlos. La API que estoy usando se basa en la DLL D2XX proporcionada por el proveedor de FTDI.

Una búsqueda rápida descubre este documento . Un breve vistazo sugiere que necesitaría abrir el dispositivo de su dispositivo para una E / S superpuesta, para implementar un servicio de escucha eficiente. Aquí hay un código de ejemplo relevante del documento (C ++):

 FT_HANDLE ftHandle; // setup by FT_W32_CreateFile for overlapped i/o char Buf[256]; DWORD dwToRead = 256; DWORD dwRead; OVERLAPPED osRead = { 0 }; osRead.hEvent = CreateEvent (NULL, FALSE, FALSE, NULL); if (!FT_W32_ReadFile(ftHandle, Buf, dwToRead, &dwRead, &osRead)) { if (FT_W32_GetLastError(ftHandle) == ERROR_IO_PENDING) { // write is delayed so do some other stuff until ... if (!FT_W32_GetOverlappedResult(ftHandle, &osRead, &dwRead, FALSE)) { // error } else { if (dwToRead == dwRead) { // FT_W32_ReadFile OK } else { // FT_W32_ReadFile timeout } } } } else { // FT_W32_ReadFile OK } 

El punto clave aquí es este comentario: ” // la escritura se retrasa, así que haga algunas otras cosas hasta que … ” [Por cierto, creo que es un error tipográfico, debería leerse en lugar de escribir allí].

¿Cómo manejar este retraso? Para hacer que su oyente sea asíncrono, tendría que esperar al objeto de evento del kernel de forma asíncrona . Acabo de responder una pregunta relacionada sobre cómo hacerlo de manera eficiente. En su caso, podría verse así (un boceto en C #):

 async Task ReadFtdiAsync() { // ... var completedMre = new ManualResetEvent(false); osRead.hEvent = completedMre.SafeWaitHandle.DangerousGetHandle(); if (!FT_W32_ReadFile(ftHandle, Buf, dwToRead, ref dwRead, ref osRead)) { if (FT_W32_GetLastError(ftHandle) == ERROR_IO_PENDING) { // asynchronous completion // read is delayed, so await var tcs = new TaskCompletionSource(); ThreadPool.RegisterWaitForSingleObject( completedMre, (s, t) => tcs.SetResult(true), null, Timeout.Infinite, true) // resume asynchronously when the event is signalled await tcs.Task; if (!FT_W32_GetOverlappedResult(ftHandle, ref osRead, ref dwRead, FALSE)) { // error throw new InvalidOperationException("I/O error"); } else { if (dwToRead == dwRead) { // FT_W32_ReadFile OK // retrieve and return the data return data; } else { // FT_W32_ReadFile timeout throw new TimeoutException(); } } } } else { // synchronous completion // retrieve and return the data // or handle an error return data; } } 

Este código puede y debe mejorarse con una lógica de cancelación y tiempo de espera adecuado Entonces simplemente puede llamar a ReadFtdiAsync en un bucle:

 while (true) { cancellationToken.ThrowIfCancellationRequested(); var data = await ReadFtdiAsync(cancellationToken); // the execution will resume here likely on a different thread ProcessData(data); } 

Si interpreto su “escucha de E / S” correctamente, entonces siempre debería tener una operación de lectura asíncrona continua en marcha. No es necesario ningún hilo.

Para E / S siempre es mejor usar puertos de finalización de E / S que en .net están envueltos en métodos como

 Stream.BeginRead Stream.BeginWrite 

o para .net 4.5

 Stream.ReadAsync Stream.WriteAsync 

es una muy mala idea poner un hilo o una tarea para esperar una operación de E / S síncrona