Tengo un programa que recibe datos una y otra vez por el puerto serie. A 0.1 segundos
¿Quieres decir que tienes declarado un
Timer que tickea/marca en un intervalo de 10 ms, o por lo contrario a 100 ms?, en el primer caso estás cometiendo un grave error de diseño que en el peor de los casos generará un elevado consumo innecesario de operaciones I/O (de CPU), así que deberías incrementar el intervalor a 100 ms, eso como mínimo (como está establecido por defecto), ya que para controlar un estado de progreso no necesitas más eficiencia, los intervalos más cortos que 100 ms se usan para cosas muy específicas que generalmente requieran mayor precisión de actividad computacional, con las consecuencias negativas y asumibles que eso conlleva.
al cerrar el programa...El programa se cuelga.
¿Alguna solución?
Lo primero de todo es que jamás debes establecer la propiedad
CheckForIllegalCrossThreadCalls (a menos que sea para realizar algún tipo de test de colisiones de hilos), asi que lo primero de todo que te aconsejo, es que para corregir el código actual, empieza por evitar el uso de esa propiedad para conservar su valor por defecto (
False).
Bien, el motivo por el cual no puedes cerrar el Form puede deberse por varios factores en combinación, sin ver el código es imposible averiguarlo, pero es causa de un algoritmo expensivo como por ejemplo un For continuo (o infinito) que no finaliza su bloque de instrucciones, el problema es que cuando el usuario demanda el cierre de la aplicación, el evento no se puede procesar hasta que el algoritmo expensivo termine de finalizar su ejecución, puesto que todas las operacioens las estás llevando a cabo de forma sincrónica en un mismo hilo, el hilo de la UI.
El problema se puede solucionar de varias maneras, te indicaré los pasos de la que considero la más sencilla:
1. Declara una variable booleana que servirá para detectar (y más tarde controlar) la petición del cierre de aplicación por parte del usuario, llamemos a esa variable por ejemplo:
userRequestedAppClose.
2. Declara un controlador de eventos (event-handler) para el evento
Form.Closing, y en el bloque de dicho event-handler controlas el valor de la propiedad
e.Closereason delos datos del evento, para asignarle un valor a nuestra variable
userRequestedAppClose en caso de
CloseReason.UserClosing.
3. Mueve la lógica del algoritmo que se encarga de leer el puerto serie y actualizar la barra de progreso, a un método individual.
Y añade las correspondientes evaluaciones para evitar excepciones de colisiones entre threads, lee acerca de
Control.Invokehttps://msdn.microsoft.com/en-us/library/system.windows.forms.control.invokerequired%28v=vs.100%29.aspx+
https://msdn.microsoft.com/en-us/library/system.windows.forms.control.invoke%28v=vs.110%29.aspx4. Declara una tarea (
System.Threading.Tasks.Task) y declara e instancia un token de cancelación (
System.Threading.CancellationToken), con la tarea podrás ejecutar el método individual del punto nº3 de forma asíncrona, asegúrate de utilizar la sobrecarga (u overload) del constructor que toma como parámetro un token de cancelación:
https://msdn.microsoft.com/es-es/library/dd783029(v=vs.110).aspx o también puedes usar este otro overload:
https://msdn.microsoft.com/es-es/library/dd783257(v=vs.110).aspx +
TaskCreationOptions.LongRunningEl token de cancelación servirá para interrumpir la ejecución de la tarea cuando el usuario demande el cierre de la app, y así poder salir.
5. Por último, debes modificar la lógica del método individual del punto nº3 para cancelar la tarea evaluando el valor de la propiedad
IsCancellationRequested según el valor actual de nuestra variable
userRequestedAppClose:
https://msdn.microsoft.com/en-us/library/system.threading.cancellationtoken.iscancellationrequested(v=vs.110).aspxUn ejemplo parcial y en pseudo-codigo:
declaración t As Task
declaración ct As CancellationToken
declaración cts As CancellationTokenSource
Método(...) {
If this.userRequestedAppClose = True, entonces:
cts.Cancel()
continuar el bloque.
de lo contrario:
continuar el bloque.
If ct.IsCancellationRequested = False, entonces:
Recoger nuevos datos del SerialPort.
Actualizar +n la posición de la barra de progreso.
de lo contrario:
Terminar ejecución del bloque.
}
Es mucho más sencillo y comprensible de lo que parece, en apenas unas 20-30 lineas de código puedes desarrollar la solución (sin sumar tu algoritmo del serialport/progressbar), simplemente he escrito explicaciones bastante largas para lo que es, ya que como te habrás dado cuenta quiero evitar mostrarte un código ya hecho, aunque de todas formas en MSDN y Google puedes encontrar cientos de ejemplos sobre tareas y cancelaciones de tarea, eres libre de escoger el camino que prefieras (es decir, copiar el código, o aprender a hacerlo por ti mismo).
Otra solución sería utilizar la class
BackgroundWorker o la class
Thread para llevar a cabo practicamente lo mismo, ya que son sencillamente lo mismo ...classes para representar un hilo de .NET, lo que las diferencia son su modo de empleo con niveles distintos de abstracción (siendo la class
Thread el low-level del threading). La class
Task tiene el nivel de abstracción más alto, por lo que en términos de comprensión y tamién implementación o elaboración, eso lo convierte en la solución más sencilla y rápida de aplicar, al menos,
en mi opinión.
Saludos.