2009-01-20 15 views
15

Estoy intentando utilizar la propiedad .BaseStream del .NET2.0 SerialPort para realizar lecturas y escrituras asincrónicas (BeginWrite/EndWrite, BeginRead/EndRead).Cómo utilizar correctamente el puerto serie .NET2.0 .BaseStream para la operación asincrónica

Tengo cierto éxito en esto, pero después de un tiempo, noto (usando Process Explorer) un aumento muy gradual en los Handles que está usando la aplicación, y ocasionalmente un hilo adicional, que también aumenta el conteo de Handle.

La tasa de cambio de contexto también aumenta cada vez que aparece un nuevo hilo.

La aplicación envía constantemente 3 bytes a un dispositivo PLC, y obtiene 800 o más bytes, a cambio, y lo hace a una velocidad de transmisión de 57600.

El cDesconecte Delta inicial (de nuevo, desde el explorador de procesos) es alrededor de 2500, lo que parece muy alto de todos modos. Cada vez que aparece un nuevo hilo, este valor aumenta y la carga de la CPU aumenta en consecuencia.

Espero que alguien haya hecho algo similar, y pueda ayudarme, o incluso decir 'En nombre de Dios, no lo hagas de esa manera'.

En el código siguiente, 'this._stream' se obtiene de SerialPort.BaseStream, y CommsResponse es una clase que uso como el objeto de estado IAsyncresult.

Este código es común a una conexión TCP que hago como alternativa al uso del puerto serie (tengo una clase base CommsChannel, con un canal serial y TCP derivado de ella) y no tiene ninguno de estos problemas, así que Tengo la esperanza razonable de que no haya nada de malo en la clase CommsResponse.

Cualquier comentario recibido con gratitud.

/// <summary> 
    /// Write byte data to the channel. 
    /// </summary> 
    /// <param name="bytes">The byte array to write.</param> 
    private void Write(byte[] bytes) 
    { 
     try 
     { 
      // Write the data to the port asynchronously. 
      this._stream.BeginWrite(bytes, 0, bytes.Length, new AsyncCallback(this.WriteCallback), null); 
     } 
     catch (IOException ex) 
     { 
      // Do stuff. 
     } 
     catch (ObjectDisposedException ex) 
     { 
      // Do stuff. 
     } 
    } 

    /// <summary> 
    /// Asynchronous write callback operation. 
    /// </summary> 
    private void WriteCallback(IAsyncResult ar) 
    { 
     bool writeSuccess = false; 

     try 
     { 
      this._stream.EndWrite(ar); 
      writeSuccess = true; 
     } 
     catch (IOException ex) 
     { 
      // Do stuff. 
     } 

     // If the write operation completed sucessfully, start the read process. 
     if (writeSuccess) { this.Read(); } 
    } 

    /// <summary> 
    /// Read byte data from the channel. 
    /// </summary> 
    private void Read() 
    { 
     try 
     { 
      // Create new comms response state object. 
      CommsResponse response = new CommsResponse(); 

      // Begin the asynchronous read process to get response. 
      this._stream.BeginRead(this._readBuffer, 0, this._readBuffer.Length, new AsyncCallback(this.ReadCallback), response); 
     } 
     catch (IOException ex) 
     { 
      // Do stuff. 
     } 
     catch (ObjectDisposedException ex) 
     { 
      // Do stuff. 
     } 
    } 

    /// <summary> 
    /// Asynchronous read callback operation. 
    /// </summary> 
    private void ReadCallback(IAsyncResult ar) 
    { 
     // Retrieve the comms response object. 
     CommsResponse response = (CommsResponse)ar.AsyncState; 

     try 
     { 
      // Call EndRead to complete call made by BeginRead. 
      // At this point, new data will be in this._readbuffer. 
      int numBytesRead = this._stream.EndRead(ar); 

      if (numBytesRead > 0) 
      { 
       // Create byte array to hold newly received bytes. 
       byte[] rcvdBytes = new byte[numBytesRead]; 

       // Copy received bytes from read buffer to temp byte array 
       Buffer.BlockCopy(this._readBuffer, 0, rcvdBytes, 0, numBytesRead); 

       // Append received bytes to the response data byte list. 
       response.AppendBytes(rcvdBytes); 

       // Check received bytes for a correct response. 
       CheckResult result = response.CheckBytes(); 

       switch (result) 
       { 
        case CheckResult.Incomplete: // Correct response not yet received. 
         if (!this._cancelComm) 
         { 
          this._stream.BeginRead(this._readBuffer, 0, this._readBuffer.Length, 
           new AsyncCallback(this.ReadCallback), response); 
         } 
         break; 

        case CheckResult.Correct: // Raise event if complete response received. 
         this.OnCommResponseEvent(response); 
         break; 

        case CheckResult.Invalid: // Incorrect response 
         // Do stuff. 
         break; 

        default: // Unknown response 
         // Do stuff. 
         break; 
       } 
      } 
      else 
      { 
       // Do stuff. 
      } 
     } 
     catch (IOException ex) 
     { 
      // Do stuff. 
     } 
     catch (ObjectDisposedException ex) 
     { 
      // Do stuff. 
     } 
    } 

Respuesta

5

Algunas sugerencias:

Dado que sólo está enviando 3 bytes, que podrían tener una operación de escritura síncrona. La demora no sería un gran problema.

Además, no cree una nueva AsyncCallback todo el tiempo. Cree una AsyncCallback de lectura y otra de escritura y úsela en cada llamada de inicio.

+1

Gracias por su respuesta. Buena sugerencia sobre la creación de las devoluciones de llamada. Lo he intentado, pero la tasa de cambio de mango/hilo/contexto aún aumenta, aunque parece aumentar a un ritmo más lento, por lo que es una mejora. – Andy

4

No necesita nada para BeginWrite. Solo envía 3 bytes, encajarán fácilmente en el búfer de transmisión y siempre estará seguro de que el búfer está vacío cuando envíe el siguiente conjunto.

Tenga en cuenta que los puertos serie son mucho más lentos que las conexiones TCP/IP. Es bastante probable que termine llamando a BeginRead() por cada byte que reciba. Eso le da al grupo de subprocesos un buen entrenamiento, definitivamente vería muchos cambios de contexto. No estoy tan seguro sobre el consumo de manejo. Asegúrese de probarlo sin el depurador adjunto.

Probar DataReceived en lugar de BeginRead() es definitivamente algo que debes probar. Tire en lugar de presionar, usará un hilo de subprocesos cuando hay algo que sucede en lugar de tener siempre uno activo.

+0

Tomo el punto con respecto a BeginWrite, no es realmente necesario. BeginRead no se activa para cada byte, pero se dispara con bastante frecuencia. ¿Tal vez podría usar DataReceived y pasar los bytes a una transmisión propia para mantener la clase constante? También estoy probando una versión 'Release', BTW. – Andy

+0

@Hans: como dijo Andy, 'BeginRead' /' EndRead' maneja fácilmente múltiples bytes por llamada (dependiendo de la configuración del tiempo de espera). Y S.IO.P.SerialPort bloquea un hilo de subprocesos en todo momento para detectar datos e iniciar DataReceived, por lo que imagina un menor uso de recursos. En realidad, hay menos llamadas al kernel necesarias para 'BeginRead' /' EndRead' que con la solución detectar-actividad-luego-leer. –

0

¿Es posible tomar los datos que vienen del puerto serie y enviarlos directamente a un archivo? A altas tasas de baudios (1 MegaBaud) es difícil manejar esta cantidad de datos sin paradas.

0

¿La respuesta del dispositivo es siempre de tamaño fijo? Si es así, intente usar SerialPort.Read y pase el tamaño del paquete. Esto bloqueará, así que combínelo con DataReceived.Mejor aún, si la respuesta siempre termina con los mismos caracteres, y se garantiza que esta firma final es única en el paquete, configure la propiedad NewLine y use ReadLine. Esto lo inmunizará contra futuros cambios en el tamaño del paquete.

+0

Desafortunadamente, los tamaños de los paquetes son variables. – Andy

+0

Bummer. Entonces, ¿cómo sabes cuándo has recibido un paquete completo? – mtrw

+0

El paquete contiene el encabezado y los bytes del terminador, con la longitud del paquete y la suma de verificación codificada dentro de él. – Andy

Cuestiones relacionadas