none
Timer se detiene mientras se realizan procesos internos RRS feed

  • Pregunta

  • Buenas tengo un cronometro que he creado usando tres timers y pasando la información a unas etiquetas en donde se muestran horas, minutos y segundos. El problema que tengo es que cuando cronometro o sea registro un tiempo en el cronometro, lo traslado a un procedimiento que realiza varias funciones, resta diferencias de tiempo, etc.

    El problema es que mientras la computadora realiza estas funciones los timers se detienen y pierden unos segundos cuando regresa a la actividad. Cómo puedo lograr eliminar este efecto?


    Frank Cruz

    jueves, 17 de mayo de 2018 0:50

Respuestas

  • Efectivamente, si el Timer que estás usando es el System.Windows.Forms.Timer, entonces éste depende para su funcionamiento de que se están bombeando los mensajes de ventana. Si tu programa realiza alguna tarea que lo mantenga ocupado y por lo tanto no pueda bombear mensajes, el Timer pierde esos Ticks.

    Una solución es forzar al programa a que bombee los mensajes. Esto se consigue mediante una llamada a Application.DoEvents() que tendrás que introducir dentro de esos procedimientos que mencionabas que mantienen al programa ocupado. Es necesario que esta llamada se ejecute lo bastante frecuentemente para que no se llege a perder ningún Tick.

    Otra solución es hacer que esos procedimientos no bloqueen la bomba de mensajes. Esto se consigue ejecutándolos en otro hilo, por ejemplo, usando un BackgroundWorker o un Thread.

    Y otra solución es usar un Timer distinto, que no dependa de la bomba de mensajes. Puedes usar un System.Threading.Timer, o un System.Timers.Timer. Pero ojo: estos Timers se disparan en un Thread distinto, por lo que no es lícito que modifiquen nada en pantalla. Si tienen que actualizar un Label o similar, se requiere transferir la ejecución de vuelta al hilo principal... cosa que solo podrás hacer si dicho hilo principal no está ocupado, por lo que no funcionará si está bloqueado ejecutando alguno de tus procedimientos. El remedio para esto sería una vez más que dichos procedimientos rueden en un Thread distinto.


    jueves, 17 de mayo de 2018 6:23
  • Bueno gracias a ambos por orientarme un poco de lo que debía hacer, les comento que usé el BackgroundWorker, en el evento click del DataGridView puse el RunWorkerAsync y en el BackgroundWorker1_DoWork puse el código que ejecutaba en el evento click del DataGridView y solucionado el problema.

    Private Sub Tabla1_CellClick(sender As Object, e As DataGridViewCellEventArgs) Handles Tabla1.CellClick
            BackgroundWorker1.RunWorkerAsync()

    End sub

    BackgroundWorker1_DoWork

    'Código a ejecutar.

    Gracias de nuevo porque me orientaron a seguir el paso correcto.


    Frank Cruz

    • Marcado como respuesta Frank Jarquin jueves, 17 de mayo de 2018 16:02
    jueves, 17 de mayo de 2018 16:02

Todas las respuestas

  • Efectivamente, si el Timer que estás usando es el System.Windows.Forms.Timer, entonces éste depende para su funcionamiento de que se están bombeando los mensajes de ventana. Si tu programa realiza alguna tarea que lo mantenga ocupado y por lo tanto no pueda bombear mensajes, el Timer pierde esos Ticks.

    Una solución es forzar al programa a que bombee los mensajes. Esto se consigue mediante una llamada a Application.DoEvents() que tendrás que introducir dentro de esos procedimientos que mencionabas que mantienen al programa ocupado. Es necesario que esta llamada se ejecute lo bastante frecuentemente para que no se llege a perder ningún Tick.

    Otra solución es hacer que esos procedimientos no bloqueen la bomba de mensajes. Esto se consigue ejecutándolos en otro hilo, por ejemplo, usando un BackgroundWorker o un Thread.

    Y otra solución es usar un Timer distinto, que no dependa de la bomba de mensajes. Puedes usar un System.Threading.Timer, o un System.Timers.Timer. Pero ojo: estos Timers se disparan en un Thread distinto, por lo que no es lícito que modifiquen nada en pantalla. Si tienen que actualizar un Label o similar, se requiere transferir la ejecución de vuelta al hilo principal... cosa que solo podrás hacer si dicho hilo principal no está ocupado, por lo que no funcionará si está bloqueado ejecutando alguno de tus procedimientos. El remedio para esto sería una vez más que dichos procedimientos rueden en un Thread distinto.


    jueves, 17 de mayo de 2018 6:23
  • Para darle más seguridad, el consejo de Alberto es 100% acertado.  Eso sí, yo en lo personal ni siquiera le hubiera dado la posibilidad de DoEvents(); no hubiera mencionado esa posibilidad porque al final hace que las cosas se confundan.  Si hay trabajos fuertes que hacer, siempre debe uno buscar hacerlos en hilos separados.  El hilo de la UI siempre debe dejarse desocupado tanto como sea posible para tener una interfaz amigable con el usuario.

    En resumen:  Use un BackgroundWorker o un hilo.  Async/Await no le funcionará a menos que sea un programa de consola.


    Jose R. MCP
    My GIT Repositories | Mis Repositorios GIT

    jueves, 17 de mayo de 2018 7:16
    Moderador
  • Buen día muchas gracias por sus respuestas y opciones, les agradecería mucho me ayudaran con un ejemplo de como implementarlo en mi cronómetro. Me saltan dudas: qué librerias debo llamar en mi aplicación para el uso de System.Threading.Timer, o un System.Timers.Timer?. Dondé debo colocar el código a implementar, en un módulo de clase o en otro lugar? cómo inicializo los segundos, minutos, horas, etc o es igual a un control timer.?

    Mejor aún cómo uso el backgroundWorker para encapsular mi código dentro de él?, ese código se activa cuando doy clic en una fila de un datagridview, el evento cellclick desencadena todos los procedimientos y llamado de funciones que se desarrollan dentro de ese evento cellclick.

    agradezco sus respuestas.


    Frank Cruz



    jueves, 17 de mayo de 2018 14:42
  • Bueno gracias a ambos por orientarme un poco de lo que debía hacer, les comento que usé el BackgroundWorker, en el evento click del DataGridView puse el RunWorkerAsync y en el BackgroundWorker1_DoWork puse el código que ejecutaba en el evento click del DataGridView y solucionado el problema.

    Private Sub Tabla1_CellClick(sender As Object, e As DataGridViewCellEventArgs) Handles Tabla1.CellClick
            BackgroundWorker1.RunWorkerAsync()

    End sub

    BackgroundWorker1_DoWork

    'Código a ejecutar.

    Gracias de nuevo porque me orientaron a seguir el paso correcto.


    Frank Cruz

    • Marcado como respuesta Frank Jarquin jueves, 17 de mayo de 2018 16:02
    jueves, 17 de mayo de 2018 16:02
  • EDITADO: Perdón, escribí esta respuesta (y la siguiente) antes de ver que ya habías dado el tema por resuelto. No obstante, dejo estas dos respuestas en el foro por si le sirven a alguien más.

    Lo primero, olvídate de las opciones del System.Threading.Timer y el System.Timers.Timer. Aunque estos Timers sí que se disparan todo el rato aunque el hilo principal esté ocupado, el problema es que no pueden pintar nada en pantalla mientras que dicho hilo esté ocupado, y por lo tanto no resuelven tu problema concreto de presentar el cronómetro (en cambio sí que te serían útiles si quisieras ejecutar periódicamente alguna operación que no pintase nada en pantalla).

    Por lo tanto, todas las soluciones pasan por trasladar tus procedimientos a un hilo independiente. Y una vez que hagas eso, entonces ya te da lo mismo seguir utilizando el Timer normal y corriente, así que no merece la pena que te compliques con los otros Timers diferentes.

    No obstante, en cuanto a la pregunta de qué librería necesitas, basta conque acudas a la documentación en MSDN y te fijes en donde dice "Assembly". Por ejemplo, para el System.Threading.Timer tienes la información aquí:

    https://msdn.microsoft.com/en-us/library/system.threading.timer(v=vs.110).aspx

    Fíjate que dice Assembly: mscorlib (in mscorlib.dll). Esa libreria siempre viene incluida en todos los proyectos, por lo que no tienes que añadir nada más, pero si se necesitase alguna otra ahí verías cuál es.

    Y fíjate también en ese mismo documento bajando en la página, viene un ejemplo bastante claro y completo. En general, cuando te enfrentes a una clase nueva, búscala siempre lo primero en la documentación de MSDN, que suele ser bastante buena, por lo menos en lo que se refiere a la funcionalidad básica del Framework.


    jueves, 17 de mayo de 2018 16:07
  • Y en cuanto a lo del BackgroundWorker, lo que harías es cortar todo el código que tienes en el CellClick y sacarlo a una subrutina. Llamémosle "Procesar", por ponerle un nombre. Entonces en el CellClick lo que haces es instanciar un BackGroundWorker, en su evento DoWork le conectas el método "Procesar", y lo pones en marcha llamando a su método RunWorkerAsync.

    Haz lo que te dije antes de buscarlo en MSDN y leer el documento, y fíjate en los ejemplos que son bastante completos.

    Recuerda que desde el método Procesar (que ahora rueda en otro hilo) no podrás pintar nada en la pantalla salvo que tomes precauciones especiales para hacerlo. Si no hace nada más que cálculos internos, llamadas a base de datos, accesos a disco, etc., entonces no hay que hacer nada, pero si ese procedimiento cambia algo en la pantalla (por ejemplo, un indicador de progreso) entonces hay que hacer cosas especiales para ello. Pero no nos adelantemos, primero cerciorémonos de que consigues poner en parcha el backgroundworker y que te funciona el cronómetro, y una vez que eso esté validado, si fuera necesario podemos tratar el tema del acceso a la pantalla.

    jueves, 17 de mayo de 2018 16:14