Pregunta

Tengo un feo código de puerto serie que es muy inestable.

void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    Thread.Sleep(100);
    while (port.BytesToRead > 0)
    {
        var count = port.BytesToRead;

        byte[] buffer = new byte[count];

        var read = port.Read(buffer, 0, count);

        if (DataEncapsulator != null)
            buffer = DataEncapsulator.UnWrap(buffer);


       var response = dataCollector.Collect(buffer);

       if (response != null)
       {
           this.OnDataReceived(response);
       }

       Thread.Sleep(100);
    }    
}

Si elimino cualquiera de las llamadas Thread.Sleep (100), el código deja de funcionar.

Por supuesto, esto realmente ralentiza las cosas y si ingresan muchos flujos de datos, deja de funcionar también a menos que haga que el sueño sea aún más grande. (Deja de funcionar como en un punto muerto puro)

Tenga en cuenta que DataEncapsulator y DataCollector son componentes proporcionado por MEF, pero su rendimiento es bastante bueno.

La clase tiene un método Listen () que inicia un trabajador en segundo plano para recibir datos.

public void Listen(IDataCollector dataCollector)
{
    this.dataCollector = dataCollector;
    BackgroundWorker worker = new BackgroundWorker();

    worker.DoWork += new DoWorkEventHandler(worker_DoWork);
    worker.RunWorkerAsync();
}

void worker_DoWork(object sender, DoWorkEventArgs e)
{
    port = new SerialPort();

    //Event handlers
    port.ReceivedBytesThreshold = 15;
    port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived);

    ..... remainder of code ...

¡Las sugerencias son bienvenidas!

Actualización: * Solo una nota rápida sobre lo que hacen las clases IDataCollector. No hay forma de saber si todos los bytes de los datos que se han enviado se leen en una sola operación de lectura. Así que cada vez que se leen datos es pasado al DataColllector que devuelve verdadero cuando se completa y Se ha recibido un mensaje de protocolo válido. En este caso aquí solo busca un byte de sincronización, longitud, crc y byte de cola. El verdadero trabajo se hace más tarde por otras clases. *

Actualización 2: Reemplacé el código ahora como se sugirió, pero aún hay algo mal:

void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
        var count = port.BytesToRead;

        byte[] buffer = new byte[count];

        var read = port.Read(buffer, 0, count);

        if (DataEncapsulator != null)
            buffer = DataEncapsulator.UnWrap(buffer);

        var response = dataCollector.Collect(buffer);

        if (response != null)
        {
            this.OnDataReceived(response);
        }     
}

Ves que esto funciona bien con una conexión rápida y estable. Pero OnDataReceived NO se llama cada vez que se reciben datos. (Consulte los documentos de MSDN para obtener más información). Entonces, si los datos se fragmentan y solo lee una vez dentro del evento, los datos se pierden.

Y ahora recuerdo por qué tuve el bucle en primer lugar, porque en realidad tiene que leer varias veces si la conexión es lenta o inestable.

Obviamente no puedo volver a la solución del bucle while, entonces, ¿qué puedo hacer?

¿Fue útil?

Solución

Mi primera preocupación con el fragmento de código original basado en while es la asignación constante de memoria para el búfer de bytes. Poner un " nuevo " La declaración aquí específicamente va al administrador de memoria .NET para asignar memoria para el búfer, mientras toma la memoria asignada en la última iteración y la envía de vuelta al grupo no utilizado para la eventual recolección de basura. Parece mucho trabajo por hacer en un ciclo relativamente cerrado.

Tengo curiosidad sobre la mejora del rendimiento que obtendría al crear este búfer en tiempo de diseño con un tamaño razonable, digamos 8K, por lo que no tiene toda esta asignación de memoria y desasignación y fragmentación. ¿Eso ayudaría?

private byte[] buffer = new byte[8192];

void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    Thread.Sleep(100);
    while (port.BytesToRead > 0)
    {
        var count = port.BytesToRead;

        var read = port.Read(buffer, 0, count);

        // ... more code   
    }
}

Mi otra preocupación con la reasignación de este búfer en cada iteración del ciclo es que la reasignación puede ser innecesaria si el búfer ya es lo suficientemente grande. Considere lo siguiente:

  • Iteración de bucle 1: 100 bytes recibidos; asignar buffer de 100 bytes
  • Iteración de bucle 2: 75 bytes recibidos; asignar buffer de 75 bytes

En este escenario, realmente no es necesario reasignar el búfer, porque el búfer de 100 bytes asignados en la iteración de bucle 1 es más que suficiente para manejar los 75 bytes recibidos en la iteración de bucle 2. No es necesario para destruir el búfer de 100 bytes y crear un búfer de 75 bytes. (Esto es discutible, por supuesto, si solo crea estáticamente el búfer y lo saca del bucle por completo).

En otra tangente, podría sugerir que el ciclo DataReceived se preocupe solo por la recepción de los datos. No estoy seguro de lo que están haciendo esos componentes MEF, pero me pregunto si su trabajo debe hacerse en el bucle de recepción de datos. ¿Es posible que los datos recibidos se pongan en algún tipo de cola y los componentes MEF puedan recogerlos allí? Estoy interesado en mantener el ciclo DataReceived lo más rápido posible. Quizás los datos recibidos se pueden poner en una cola para que puedan volver al trabajo y recibir más datos. Puede configurar otro hilo, tal vez, para observar los datos que llegan a la cola y hacer que los componentes de MEF recojan los datos de allí y hagan su trabajo desde allí. Puede que sea más codificación, pero puede ayudar a que el bucle de recepción de datos sea lo más receptivo posible.

Otros consejos

Y puede ser tan simple ...

Puede usar el controlador DataReceived pero sin un bucle y ciertamente sin Sleep (), lea qué datos están listos y póngalos en algún lugar (a una Cola o MemoryStream),

o

Inicie un subproceso (BgWorker) y haga un (serial) serialPort1.Read (...), y nuevamente, empuje o reúna los datos que obtenga.

Editar:

De lo que publicaste, diría: suelta el controlador de eventos y solo lee los bytes dentro de Dowork (). Tiene el beneficio de que puede especificar cuántos datos desea, siempre que sean (mucho) más pequeños que ReadBufferSize.

Edit2, con respecto a Update2:

Aún será mucho mejor con un ciclo while dentro de un BgWorker, sin usar el evento en absoluto. La forma simple:

byte[] buffer = new byte[128];  // 128 = (average) size of a record
while(port.IsOpen && ! worker.CancelationPending)
{
   int count = port.Read(buffer, 0, 128);
   // proccess count bytes

}

Ahora tal vez sus registros sean de tamaño variable y no quiera esperar a que lleguen los siguientes 126 bytes para completar uno. Puede ajustar esto reduciendo el tamaño del búfer o estableciendo un ReadTimeOut. Para obtener un grano muy fino, puede usar port.ReadByte (). Dado que eso se lee desde el ReadBuffer, en realidad no es más lento.

Si desea escribir los datos en un archivo y el puerto serie se detiene cada cierto tiempo, esta es una forma sencilla de hacerlo. Si es posible, haga que su búfer sea lo suficientemente grande como para contener todos los bytes que planea colocar en un solo archivo. Luego escriba el código en su controlador de eventos de datos recibidos como se muestra a continuación. Luego, cuando tenga la oportunidad, escriba todo el búfer en un archivo como se muestra a continuación. Si debe leer DESDE su búfer mientras el puerto serie está leyendo A su búfer, intente usar un objeto de flujo en búfer para evitar puntos muertos y condiciones de carrera.

private byte[] buffer = new byte[8192]; 
var index = 0;
void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{    
    index += port.Read(buffer, index, port.BytesToRead);
} 

void WriteDataToFile()
{
    binaryWriter.Write(buffer, 0, index); 
    index = 0;
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top