Pergunta

Eu tenho um pedaço feio de código de porta serial que é muito instável.

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

Se eu remover qualquer Thread.Sleep (100) chama o código pára de funcionar.

É claro que isto atrasa as coisas e se lotes de fluxos de dados em, ele pára de funcionar bem, a menos que eu faço o sono ainda maior. (Pára de funcionar como no impasse pura)

Por favor, note o DataEncapsulator e coletor de dados são componentes fornecida pelo MEF, mas seu desempenho é muito bom.

A classe tem um método Listen () que inicia um trabalho de fundo para receber dados.

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 ...

As sugestões são bem-vindos!

Update: * Apenas uma nota rápida sobre o que as classes IDataCollector fazer. Não há nenhuma maneira de saber se todos os bytes dos dados que foram enviados são lidos em uma única operação de leitura. Então toda vez que os dados são lidos é passado para o DataColllector que retorna true quando um completo e mensagem de protocolo válido foi recebido. Neste caso aqui apenas verifica um byte de sincronismo, o comprimento, CRC e cauda byte. O verdadeiro trabalho é feito mais tarde por outras classes. *

Update 2: Eu substituí o código agora como sugerido, mas ainda há algo errado:

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

Você vê isso funciona bem com um rápido e conexão estável. Mas OnDataReceived não é chamado sempre que dados são recebidos. (Veja a documentação do MSDN para mais). Então, se os dados são fragmentados e você lê somente uma vez dentro os dados do evento se perde.

E agora eu me lembro por que eu tinha o loop, em primeiro lugar, porque ele realmente tem que ler várias vezes se a conexão é lenta ou instável.

Obviamente eu não posso voltar para a solução loop while, então o que eu posso fazer?

Foi útil?

Solução

A minha primeira preocupação com o fragmento de código baseado em enquanto original é a alocação constante de memória para o buffer de bytes. Colocar uma "nova" declaração aqui especificamente indo para o gerenciador de memória .NET para alocar memória para o buffer, tendo a memória alocada na última iteração e enviá-lo de volta para a piscina sem uso por eventual coleta de lixo. Isso parece uma enorme quantidade de trabalho a fazer em um loop relativamente apertado.

Estou curioso quanto à melhoria de desempenho que você ganharia por criar este tampão em tempo de design com um tamanho razoável, dizem 8K, então você não tem tudo isso alocação de memória e desalocação e fragmentação. Será que isso ajuda?

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

A minha outra preocupação com a re-alocar este tampão em cada iteração do loop é que a realocação pode ser desnecessário se o buffer já é grande o suficiente. Considere o seguinte:

  • iteração do loop 1: 100 bytes recebidos; alocar buffer de 100 bytes
  • iteração do loop 2: 75 bytes recebidos; alocar buffer de 75 bytes

Neste cenário, você não precisa realmente de re-alocar o buffer, porque o buffer de 100 bytes alocados na iteração do loop 1 é mais do que suficiente para lidar com os 75 bytes recebidos em iteração do loop 2. Não há necessidade para destruir o buffer de 100 bytes e criar um tampão de 75 byte. (Este é discutível, claro, se você apenas estaticamente criar o tampão e movê-lo fora do circuito por completo.)

Em outra tangente, eu poderia sugerir que o loop DataReceived preocupar-se apenas com a recepção dos dados. Eu não sei o que esses componentes MEF estão fazendo, mas eu pergunta se seu trabalho tem de ser feito no circuito de recepção de dados. É possível que os dados recebidos para ser colocado em algum tipo de fila e os componentes do MEF pode pegá-los lá? Estou interessado em manter o ciclo DataReceived tão rápida quanto possível. Talvez os dados recebidos podem ser colocados em uma fila para que ele possa voltar direito ao trabalho de receber mais dados. Você pode configurar outro segmento, talvez, esteja atento aos dados que chegam na fila e ter os componentes MEF pegar os dados de lá e fazer o seu trabalho de lá. Isso pode ser mais de codificação, mas pode ajudar o loop recepção de dados ser tão sensível quanto possível.

Outras dicas

E isso pode ser tão simples ...

Ou você usa manipulador DataReceived mas sem um loop e, certamente, sem Sleep (), ler o que os dados estão prontos e empurrá-lo em algum lugar (para uma fila ou MemoryStream),

ou

iniciar uma discussão (BgWorker) e fazer um serialPort1.Read (bloqueio) (...) e, novamente, empurrar ou montar os dados que você começa.

Editar:

A partir do que você postou, eu diria: soltar o eventhandler e basta ler os bytes dentro DoWork (). Isso tem a vantagem que você pode especificar a quantidade de dados que você quiser, contanto que ele é (muito) menor do que o ReadBufferSize.

Edit2, em relação Update2:

Você ainda vai ser muito melhor com um tempo de loop dentro de um BgWorker, não usando o evento em tudo. A maneira mais simples:

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

}

Agora, talvez os seus registros são de tamanho variável e você não não quer esperar para os próximos 126 bytes para vir a uma completa. Pode sintonizar isso, reduzindo o tamanho da memória intermédia ou definir um ReadTimeout. Para chegar muito de grão fino que você poderia usar port.ReadByte (). Desde que lê a partir do ReadBuffer não é realmente mais lento.

Se você quiser gravar os dados em um arquivo e a porta serial pára de vez em quando esta é uma maneira simples de fazê-lo. Se possível faça seu buffer grande o suficiente para armazenar todos os bytes que você pretende colocar em um único arquivo. Em seguida, escrever o código em seu manipulador de eventos DataReceived como mostrado abaixo. Então, quando você começa uma oportunidade escrever todo o buffer para um arquivo, como mostrado abaixo. Se você deve ler a partir de seu tampão enquanto a porta serial está lendo para seu buffer, em seguida, tente usar um objeto de fluxo tampão para evitar impasses e as condições de corrida.

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 em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top