C# BasicLinkedList: Listas enlazadas

Vamos a seguir desarrollando el post anterior (“C# Clases autorreferenciadas“) para generar una lista enlazada, una lista enlazada es una colección lineal o una secuencia de nodos representados con clases autorreferenciadas. El acceso a la lista se realiza desde el primero nodo (raíz o head) y se recorre accediendo al miembro que apunta al siguiente nodo y así hasta el final (el miembro del último nodo que apunta al siguiente se define como null para evitar errores). Normalmente las listas tienen las operaciones comunes para trabajar con sus nodos, vamos a definir métodos para:

  • Construir una lista vacía y darle un nombre.
  • Insertar un nodo en la cabecera.
  • Insertar un nodo al final.
  • Eliminar el primer nodo de la lista.
  • Eliminar un nodo del final de la lista.
  • Comprobar si una lista está vacía.
  • Imprimir por pantalla el contenido.
  • Obtener el número de elementos o nodos de la lista.

Clase para definir un nodo

Similar al ejemplo anterior definimos una clase que represente un nodo. Definimos dos constructores, el segundo permite crear un nodo y definir el nodo al que apunta como siguiente elemento en la lista.

Lo más remarcable de la clase es la referencia object. Nos permite almacenar tipos de datos simples como si fueran objetos. El tipo object es un alias para Object en .NET. Todos los tipos de variables son herencia directa de este tipo. Podemos asignar valores de cualquier tipo a variables de tipo object. Cuando una variable se convierte a un tipo object a esta operación se le llama boxing o boxed. Cuando se realiza la operación inversa se denomina unboxing.

Clase lista

La clase lista tiene contiene como miembros head y tail. Son respectivamente referencias al primer y último nodo de la lista, también definimos una variable string para asignar un nombre a la lista.

 

Definimos un método que nos resultará de utilidad más adelante, el método IsListEmpty retorna true si la cabeza de lista apunta a null.

Ahora definimos un método para operar sobre la lista insertando un nuevo nodo al inicio de la misma.  Si la lista esta recién creada o vacía la cabeza y la cola apunta al nuevo y único la misma. En caso contrario la cabeza apunta al nuevo nodo y le pasamos el nodo de cabeza actual como miembro para que quede en segundo lugar.

Para añadir un nodo al final definimos el siguiente método:

Antes de continuar con un método para borrar un elemento del inicio de la lista vamos a definir una clase EmptyListException para lanzar una excepción cuando se producen operaciones ilegales sobra la lista, por ejemplo si la lista está vacía. Usamos System.ApplicationException para excepciones generadas por nuestra programa.

Ahora ya podemos crear un método para borrar un elemento de la cabecera de la lista. Si la lista está vacía lanza una excepción que podemos capturar y tratar desde el programa principal. Después obtenemos el miembro del nodo de cabecera y restablece las referencias del primer y último nodo (si solo hay un nodo en la lista head y last quedaran a null, si hay más de un elemento avanzamos al siguiente nodo la cabecera).

Visto el anterior ejemplo borrar el último nodo es similar. Pero en este caso el método que debemos seguir  nodo no es muy eficiente (esto se solucionaría con una lista doblemente enlazada). Recorremos desde el primero nodo uno de detrás de otro hasta que el nodo siguiente no sea el último, de esta manera hacemos que apunte a null quedando fuera el último nodo.

Ahora solo nos queda un método para imprimir los nodos de la lista.

Ahora veamos como se puede utilizar:

Referencias externas

 

C# Clases autorreferenciadas

Una clase autorreferenciada es muy común para almacenar datos de forma dinámica y consumirlos a medida que los necesitamos en otro proceso, una clase autorreferenciada contiene un miembro de la clase que hace referencia a un objeto del mismo tipo. La unión de las clases referenciadas crea una lista de nodos que podemos recorrer accediendo de forma sucesiva desde el nodo raíz  al miembro del nodo que apunta al siguiente y así sucesivamente.

El ejemplo de abajo es como se organiza un array bidimensional en memoria, realmente se reservan zonas de memoria contiguas como en un array de una dimensión.

Frente a un array estático (los elementos se organizan en memoria de forma contigua y su tamaño se reserva en la declaración de la variable)  con un número de elementos determinados tiene una importante ventaja, una lista basada en nodos autoenlazados puede modificar su tamaño de forma dinámica en tiempo de ejecución (aunque también tiene un coste computacional que puede afectar al rendimiento de nuestro programa el manejo de grandes estructuras de memoria que reservan y liberan memoria según su necesidad).

El ejemplo de arriba es un ejemplo muy básico de un clase autorreferenciada, por supuesto los nodos pueden contener todos los datos que necesitemos de cualquier tipo.

Para ir añadiendo nuevos nodos durante la ejecución de nuestro programa haremos uso de la reserva de memoria dinámica con el operador new, este operador recibe como operando el tipo de objeto que se asignará de forma dinámica y devuelve una referencia a un objeto de este tipo.

Basándonos en esta arquitectura podemos crear listas enlazadas, colas o arboles. Cada tipo de estructura tiene propósitos diferentes y puede ser aplicado según el problema que debamos resolver.

 

 

C# SerialConsoleApp3: Gestión línea serie

Esta vez vamos evitar usar el manejador de evento o método delegado usando SerialDataReceivedEventHandler. En vez de eso vamos a tomar el control del intervalo de lectura del buffer del puerto serie. Primero añadimos el espacio de nombres System.Timers para usar la clase Timer en nuestra nueva clase COMTimer.

Clase COMTimer

La clase COMTimer solo tiene dos atributos, una clase Timer y un booleano que nos informa de si el intervalo de tiempo de espera ha expirado, el objetivo es que podamos consultar la propiedad bTimeOut en cualquier momento y comprobar si el periodo definido en mili-segundos ha transcurrido (no bloquea la ejecución de nuestro programa principal).

Constructor

Inicializamos la bTimeOut a false (se pondrá true cuando expire el intervalo). También definimos algunos atributos de la clase Timer.

  • AutoReset: indica si Timer debe generar el evento Elapsed solo una vez (false) o repetidamente (true).
  • Enabled: indica si Timer debe generar el evento Elapsed.
  • Interval: Obtiene o establece el intervalo, expresado en milisegundos, en el que se generará el evento Elapsed.
  • Elapsed: Se produce cuando transcurre el intervalo. Definimos el método OnTimeoutCOMEvt que será invocado cuanto el intervalo transcurra.

Evento OnTimeoutCOMEvt

Cuando el intervalo de tiempo expire el método OnTimeoutCOMEvt es invocado, establece bTimeOut a true y para la instancia _objCOMTimer de la clase Timer  con el método Stop.

Start

Clase COMPort

Hemos definido un nuevo método de la clase COMPort que viene a sustituir el método delegado EvtCOMDataRx. Recibe como parámetro el periodo de espera para recibir un nuevo byte (en ms).  Inicia el timer y a continuación entra en un bucle infinito hasta que expira el tiempo o hay algo para leer por el buffer serie.

Uso de la clase COMPort

En vez de usar Console.Read para para ejecución del programa al final usamos Console.KeyAvailable para saber si hay pulsación de teclado para salir de la aplicación, mientras tanto trata de leer (le pasamos 1 ms de intervalo de Tout)

C# SerialConsoleApp2: Gestión básica de la línea serie

A partir del ejercicio C# SerialConsoleApp1: Gestión básica de la línea serie vamos a seguir refinando nuestra clase básica para comunicaciones RS232.

Constructor

Hemos sobrecargado el método OpenCOMPort para simplificarlo, el nuevo método sólo recibe el parámetro con la cadena que define el puerto que queremos abrir, en la mayoría de los casos el resto de parámetros se mantienen y suelen ser los mismos.

Definimos una serie de constantes como atributos de la clase con los valores por defecto.

Destructor de la clase

También comprobamos en el destructor de la clase si el puerto aún sigue abierto y si es así lo cerramos.

GetPortList: Nuevo método

Definimos un nuevo método para obtener la lista de puertos y así poder hacer lo que queramos (no solo mostrarlos por consola), por ejemplo alimentar un selector desplegable con esta información en una aplicación de ventanas.

El nuevo método retorna una lista de cadenas. Una clase List<T> representa una colección de objetos del mismo tipo indexados en una lista por posición. Al igual que cualquier clase tiene un constructor, propiedades y métodos (Add, BinarySearch, …)

Su uso es igualmente sencillo:

Leer de la línea serie byte a byte

Vamos a mejorar ligeramente el método de lectura para leer el buffer serie un valor cada vez, esto nos permite mayor control de la información leída realizando un tratamiento de cada byte a medida que llega.

Resultado

Volvemos a conectar el Arduino del primer ejercicio para poder leer la cadena que envía.

 

C# SerialConsoleApp1: Gestión básica de la línea serie

Conector DB9 (9 pines). Para conexiones en serie, ya que permite una transmisión asíncrona de datos según lo establecido en la norma RS-232

Hoy en día la mayoría de los temarios y contenidos de cursos de lenguajes de programación se dirigen a aplicaciones y servicios en la nube o interfaces gráficos de escritorio, pocas veces se tratan temas como protocolos de comunicación entre máquinas a bajo nivel.

El protocolo serie (RS232) aún hoy es un sistema de comunicaciones muy común en maquinas industriales, es conocido, confiable y además ya existe un montón de código desarrollado en cualquier lenguaje de programación.

Por desgracia hoy día la mayoría de máquinas no traen por defecto este puerto (en los PCs industriales sin embargo es común que vengan con varios puertos COM), en su lugar usan un puerto COM emulado mediante controladores del sistema operativo sobre un cable USB (por ejemplo un dispositivo Arduino).

Inclusión del espacio de nombres

Para trabajar con los puertos COM debemos añadir las siguientes líneas al comienzo del programa (más sobre System.IO en MSDN).

Primer paso: Obtención de puertos COM del sistema

Lo primero es crear una clase básica que albergara todas nuestras funciones para trabajar con el puerto serie. La clase inicial sólo contendrá un método público para imprimir por consola una enumeración de los puertos disponibles en el sistema operativo.

Para obtener los puertos hemos empleado el método SerialPort.GetPortNames. Este método estático retorna un array de cadenas con los nombres de los puertos serie.

Como no tengo otra cosa he conectado mi placa Arduino al PC y este es el resultado.

Abriendo el puerto COM

Lo siguiente es diseñar un método para configurar y abrir el puerto serie, la función recibe los parámetros de configuración de la línea.

Lo más interesante de esta función es la definición del evento DataReceived. Hacemos que DataReceived apunte a un método nuestro (EvtCOMDataRx) que será invocado cada vez que la línea serie indique que ha recibido nuevos datos (handle o manejador), de esta forma no tenemos que estar constantemente preocupados de si han llegado nuevos datos o no.

Enviando datos de prueba de Arduino a nuestro programa

Para no perder el tiempo he usado un proyecto super básico que ilustra como enviar una cadena desde nuestra placa Arduino al PC.

 

Cuando ejecuto mi aplicación C# puedo ver los datos enviados en mi PC!

Posts relacionados

Enlaces externos

C# ThreadingBasics (II): Operaciones básicas con Threads

Código en GitHub

GetThreadState: Determinar el estado de un thread

El siguiente ejercicio nos permite obtener información valiosa sobre el estado de un Thread. Añadimos los siguientes métodos:t1

Y añadimos el siguiente código a la función principal Main:

t1

El Thread pasa por varios estados, podemos obtener el estado del hilo accediendo a la propiedad ThreadState que es un campo enumerado.

Inicialmente el estado del Thread t1 es Unstarted antes de ser arrancado. Cuando invocamos el método Start() inmediatamente pasa al estado Running. La llamada a Sleep provoca que pase al estado WaitSleepJoin. Finalmente la llamada Abort hace que pase a estado Aborted. El thread t2 finaliza con normalidad y su estado estado final es Stopped.

TheadPriority: Establecer prioridad en la ejecución de un thread


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Threading;

namespace TheadPriority
{
class ThreadSample
{
private bool _isStopped = false;

public void Stop()
{
this._isStopped = true;
}

public void CountNumbers()
{
long counter = 0;
while (!this._isStopped)
{
counter++;
}
Console.WriteLine("{0} with {1,11} priority " +
"has a count = {2,13}",
Thread.CurrentThread.Name,
Thread.CurrentThread.Priority,
counter.ToString("N0"));
}
}

class Program
{
static void RunThreads()
{
var sample = new ThreadSample();
var t1 = new Thread(sample.CountNumbers);
t1.Name = "t1";
var t2 = new Thread(sample.CountNumbers);
t2.Name = "t2";

t1.Priority = ThreadPriority.Highest;
t2.Priority = ThreadPriority.Lowest;

t1.Start();
t2.Start();

Thread.Sleep(TimeSpan.FromSeconds(10));
sample.Stop();
}

static void Main(string[] args)
{
Console.WriteLine("Main: current thread priority: {0}",
Thread.CurrentThread.Priority);
Console.WriteLine("Main: Calling RunThreads()");
RunThreads();
Console.WriteLine("Press any key to exit....");
Console.ReadLine();
}
}
}