Se me cuelga Windows Form

Iniciado por Meta, 27 Abril 2016, 01:18 AM

0 Miembros y 1 Visitante están viendo este tema.

Meta

Hola:

Tengo un programa que recibe datos una y otra vez por el puerto serie. A 0.1 segundos datos del 0 al 1023. Si no hay datos que esté leyendo porque en el otro lado no lo está enviando, puedo cerrar el programa sin problemas. Si hay datos que le llega y lee, al cerrar el programa aunque pongas SerialPort1.Close(); El programa se cuelga.

En el Load de Windwos Form he puesto esto.
Código (csharp) [Seleccionar]
Control.CheckForIllegalCrossThreadCalls = false;
https://msdn.microsoft.com/es-es/library/system.windows.forms.control.checkforillegalcrossthreadcalls%28v=vs.100%29.aspx

Se sigue colgando. No ha funcionado.

Por lo que he leído hay que usar hilos y es más complejo, no lo se.

¿Alguna solución?

Saludos.
Tutoriales Electrónica y PIC: http://electronica-pic.blogspot.com/

Eleкtro

#1
Cita de: Meta en 27 Abril 2016, 01:18 AMTengo 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.




Cita de: Meta en 27 Abril 2016, 01:18 AMal 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.Invoke
https://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.aspx

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

El 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).aspx

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








Meta

#2
Gracias, tendré que leer mucho y hacer pruebas.

El código completo es este, para que te hagas una idea.
Código (csharp) [Seleccionar]
using System;
using System.Collections.Generic;
using System.Windows.Forms;

using System.IO.Ports; // No olvidar.
using System.IO;

namespace Arduino_In_Analogico_prueba_01
{
    public partial class Form1 : Form
    {
        // Utilizaremos un string como buffer de recepción.
        string Recibidos;

        public Form1()
        {
            InitializeComponent();

            if (!serialPort1.IsOpen)
            {
                try
                {
                    serialPort1.Open();
                }
                catch (System.Exception ex)
                {
                    MessageBox.Show(ex.ToString());
                }

                serialPort1.DataReceived += new SerialDataReceivedEventHandler(serialPort1_DataReceived);
            }

           
        }


        // Al recibir datos.
        private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {

            // Acumula los caracteres recibidos a nuestro 'buffer' (string).
            try
            {
                Recibidos = serialPort1.ReadLine();
                Actualizar(null,null);
            }

            catch (IOException)
            {
                // Información adicional: La operación de E/S se anuló por una salida de subproceso o por una solicitud de aplicación.
            }

            // Invocar o llamar al proceso de tramas.
            //Invoke(new EventHandler(Actualizar));
            Actualizar();
        }


        // Como variables de clase
        private string[] Separador = new string[] { ",", "\r", "\n", "/n" };
        private List<string> Leo_Dato = new List<string>();

        // Procesar los datos recibidos en el bufer y extraer tramas completas.
        private void Actualizar(object sender, EventArgs e)
        {
            double Voltaje = 0;
            double Porcentaje = 0;

            // En el evento
            Leo_Dato.Clear();
            Leo_Dato.AddRange(Recibidos.Split(Separador, StringSplitOptions.RemoveEmptyEntries));

            //            Se produjo una excepción de tipo 'System.ArgumentOutOfRangeException' en mscorlib.dll pero no se controló en el código del usuario


            try
            {
                label_Lectura_Potenciometro.Text = Leo_Dato[0].ToString();
            }

            catch (ArgumentOutOfRangeException)
            {
                //Información adicional: El índice estaba fuera del intervalo. Debe ser un valor no negativo e inferior al tamaño de la colección.
            }


            progressBar1.Value = Convert.ToInt32(Leo_Dato[0].ToString());
            double Dato_Voltaje = Convert.ToDouble(Leo_Dato[0]);
            double Dato_Porcentaje = Convert.ToDouble(Leo_Dato[0]);

            Voltaje = Dato_Voltaje * (5.00 / 1023.00);
            Porcentaje = Dato_Porcentaje * (100.00 / 1023.00);

            label_Voltio.Text = Voltaje.ToString("N2") + " V."; // N2 tiene dos decimales.
            label_Portentaje.Text = Porcentaje.ToString("N2") + " %";
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (serialPort1.IsOpen) // ¿El puerto está abierto?
            {
                serialPort1.Close(); // Puerto cerrado.
            }
        }
    }
}


Ve complicado a modo extremo bajo mi punto de vista resolver esta situación. Lo que se me ocurre en el Closing es dar una orden al puerto serie para que deje de enviar datos al puerto, que se detenga, luego cerrar la aplicación. Si no hay dato al recibir, la aplicación o programa cierra con normalidad, sin cuelgues.

Es mi única alternativa, ya que no saco forma de lograrlo de forma formal.

;)
Tutoriales Electrónica y PIC: http://electronica-pic.blogspot.com/