none
Thread (Hilo) con un Timer RRS feed

  • Pregunta

  • Buenos días gente.

    Necesito consejo de como realizar un pequeño programita que realize lo siguiente.

    La aplicación necesita comprobar cada 5 minutos (por decir un ejemplo) cierta información de una base de datos remota. Estos datos los obtengo vía API REST desarrollada en PHP y los consumo en la aplicación VB.NET (Uso HttpClient para esto). Esta comprobación me gustaría hacerla en segundo plano para así poder hacer otras tareas.

    He probado a usar un System.Timer pero esto me bloquea la aplicación hasta que termine el Timer. He probado también a usar un Thread.Timer pero tengo problemas con las tareas async usando await... O lo mismo es que lo estoy haciendo mal....

    Espero que me ayuden en esto con algún ejemplo o algo.

    Un saludo

    viernes, 1 de febrero de 2019 8:45

Todas las respuestas

  • Tienes que haber configurado algo mal. Tanto el System.Timers.Timer como el System.Threading.Timer funcionan sin bloquear el hilo desde el que se inicializó el Timer.

    No has dicho qué clase de aplicación es. Si es una aplicación de escritorio (WPF o Winforms), entonces desde el evento del Timer no se puede modificar nada de la interfaz de usuario, dado que se ejecuta en otro hilo. Si para soslayar este problema estás trasvasando la ejecución al hilo principal mediante (por ejemplo) Control.Invoke, entonces eso sí que podría bloquear la aplicación, dependiendo de qué es lo que hagas en ese punto, dado que ya estás fuera del hilo del Timer y has vuelto al hilo principal.

    Como ves, todo depende mucho de cómo estés haciendo las cosas. Habría que analizar el detalle de lo que estás haciendo para determinar a qué puede ser debido ese bloqueo del que estás hablando. En principio es perfectamente razonable usar un timer para lanzar una petición REST mediante un HttpClient. Esto debe funcionar; si no lo hace, tiene que estar pasando alguna otra cosa que no se infiere a partir de la breve descripción que has hecho.

    viernes, 1 de febrero de 2019 9:12
  • Tienes que haber configurado algo mal. Tanto el System.Timers.Timer como el System.Threading.Timer funcionan sin bloquear el hilo desde el que se inicializó el Timer.

    No has dicho qué clase de aplicación es. Si es una aplicación de escritorio (WPF o Winforms), entonces desde el evento del Timer no se puede modificar nada de la interfaz de usuario, dado que se ejecuta en otro hilo. Si para soslayar este problema estás trasvasando la ejecución al hilo principal mediante (por ejemplo) Control.Invoke, entonces eso sí que podría bloquear la aplicación, dependiendo de qué es lo que hagas en ese punto, dado que ya estás fuera del hilo del Timer y has vuelto al hilo principal.

    Como ves, todo depende mucho de cómo estés haciendo las cosas. Habría que analizar el detalle de lo que estás haciendo para determinar a qué puede ser debido ese bloqueo del que estás hablando. En principio es perfectamente razonable usar un timer para lanzar una petición REST mediante un HttpClient. Esto debe funcionar; si no lo hace, tiene que estar pasando alguna otra cosa que no se infiere a partir de la breve descripción que has hecho.

    Gracias Alberto!!

    Es verdad que la descripción es algo corta... Tampoco quería poner una parrafada para no "liar" mucho el asunto. Tienes razón, no he especificado el tipo de aplicación, es un Windows Form...

    Lo del tema del bloqueo es algo que no ocurre así exactamente, y tampoco sabría explicar muy bien... Actualmente uso Thread.Timer que al iniciar el hilo ejecuta una clase aparte que es la que implementa la lógica de las peticiones. Mas o menos hago lo siguiente....

    Al iniciar el formulario hago lo siguiente

    timer = New Threading.Timer(AddressOf Tick, Nothing, 5000, 10000)

    y en el evento Tick tengo esto

    Private Sub Tick(ByVal data As Object) contador = contador + 1 ToolStripStatusLabel1.Text = contador If Me.chkPedidosProveedores.Checked Then 'Task.WaitAll()

    timer.Start() 'Iniciamos el proceso de comprobación de pedidos a proveedor ProcesosExternos = New ProcesosPedidosProveedores ProcesosExternos.SubirCabecerasPedidosProveedor() End If End Sub

    El check lo tengo puesto para que cuando se desmarque se pare el timer.... He de decir que esos procesos externos se ejecutan como Task y no sé si eso influye o no...

    Saludos.

    EDITO: Como aclaración, el timer lo declaro en el hilo principal... 



    • Editado osmagarci viernes, 1 de febrero de 2019 10:08
    viernes, 1 de febrero de 2019 10:07
  • Hola:

    Desde un hilo no se debería llamar a un control del hilo principal, tu tienes:

    ToolStripStatusLabel1.Text = contador

    Deberías usar .Invoke:

    https://docs.microsoft.com/es-es/dotnet/framework/winforms/controls/how-to-make-thread-safe-calls-to-windows-forms-controls

    También verifica que en tu clase tampoco haces llamadas a controles.

    Un saludo

    viernes, 1 de febrero de 2019 10:56
  • y en el evento Tick tengo esto [...]

    Veo un problema serio en tu Tick. Estás accediendo a la interfaz de usuario. En particular, llamas a ToolStripStatusLabel1 y también a chkPedidosProveedores, aparte de lo que pueda ocurrir en ProcesosExternos. La interfaz de usuario es mono-hilo y si accedes a ella desde otro hilo se pueden producir condiciones de carrera que hagan que se corrompa de manera intermitente e imprevisible, causando todo tipo de problemas incluyendo bloqueos de la aplicación. Para evitarlo, hay que trasladar la ejecución al hilo principal antes de acceder a los controles. Esto se hace mediante el método Invoke de cada control. Nótese que esto puede causar un bloqueo en el hilo secundario si el hilo principal está ocupado en el momento de hacer el Invoke.
    viernes, 1 de febrero de 2019 11:23
  • Hola:

    Desde un hilo no se debería llamar a un control del hilo principal, tu tienes:

    ToolStripStatusLabel1.Text = contador

    Deberías usar .Invoke:

    https://docs.microsoft.com/es-es/dotnet/framework/winforms/controls/how-to-make-thread-safe-calls-to-windows-forms-controls

    También verifica que en tu clase tampoco haces llamadas a controles.

    Un saludo

    Hola. He comprobado la clase y no hago referencia a ningún control del form principal. Este es el codigo del metodo que inicia el proceso dentro de la clase.

        Public Async Function SubirCabecerasPedidosProveedor() As Task
    
            Task.WaitAll()
            'SubirCabecerasPedidosProveedor()
            CabeceraPedidoProveedor = Await ComprobarPedidosProveedorMySql()
            Local_PedidosComra = ComprobarPedidos().Where(Function(x) x.dfecped >= New DateTime(2019, 1, 1)).ToList()
    
            tmp_pedidoscompra = (From ped In CabeceraPedidoProveedor
                                 Group Join c In Local_PedidosComra On c.nnumped Equals ped.nnumped Into g = Group
                                 From r In g.DefaultIfEmpty()
                                 Where r Is Nothing
                                 Select ped).ToList
    
            Dim urlCab = Me.SubirPedidos(tmp_pedidoscompra)
    
            'Obtenemos las lineas y mysql
    
            LineasPedidoProveedor = ObtenerLineasPedidos()
            LineasPedidosProveedores_remoto = Await ComprobarLineasPedidosProveedorMySql()
    
            tmp_lineaspedidoscompra = (From lin In LineasPedidosProveedores_remoto
                                       Group Join c In LineasPedidoProveedor On c.nnumped Equals lin.nnumped Into g = Group
                                       From r In g.DefaultIfEmpty()
                                       Where r Is Nothing
                                       Select lin).ToList
    
            Dim urlLin = Me.SubirLineasPedidos(tmp_lineaspedidoscompra)
    
    
    
        End Function

    Especifico un poco lo que me pasa. Al iniciar entra en la primera linea 

    CabeceraPedidoProveedor = Await ComprobarPedidosProveedorMySql()

    y obtiene los datos correctamente.

    Pero al llegar a la línea 

    Local_PedidosComra = ComprobarPedidos().Where(Function(x) x.dfecped >= New DateTime(2019, 1, 1)).ToList()

    se queda hay y no pasa. He puesto un punto de interrupción en 

            tmp_pedidoscompra = (From ped In CabeceraPedidoProveedor
                                 Group Join c In Local_PedidosComra On c.nnumped Equals ped.nnumped Into g = Group
                                 From r In g.DefaultIfEmpty()
                                 Where r Is Nothing
                                 Select ped).ToList
    Pero nunca llega hay.... es como si de la anterior no pasara...


    viernes, 1 de febrero de 2019 11:27