none
Crear o usar un control (TextBox) en un hilo distinto al principal RRS feed

  • Pregunta

  • Es una aplicación que rellena un TextBox (Muestra) desde una variable tipo string (var1) dentro de un bucle tipo while del cual nunca sale que esta al interior del control BackgroundWorker1.

    Si no actualizo el TextBox, lo que incrementa con el paso del tiempo es la memoria consumida pero la aplicación sigue funcionando.

    Pero, si uso el TextBox ya sea con llamada segura con delegado o con la opción

    CheckForIllegalCrossThreadCalls = False

    La aplicación no responde a nada ya que la velocidad de actualización es alta y los datos cada vez mayores.

    Mi solución seria crear o manejar el control (TextBox) en un hilo separado que terminaría solo cuando el hilo principal se cierre y este hilo lo único que haría es actualizar ese (TextBox)

    ¿Si hay una mejor manera que el TextBox se actualice sin interferir el funcionamiento del resto de la aplicación? Seria bueno.

    miércoles, 13 de septiembre de 2017 1:48

Respuestas

  • Buenas compañero, 

    El tema es que el aspecto puramente visual,  lo va a seguir haciendo el main thread,  ya que GDI trabaja la interfaz con un solo hilo, cuando haces invocaciones desde otros,  va a ser el main el que realice el acceso y modificación del control. 

    Si gestionas toda la lógica en otro thread y simplemente vas lanzando invocaciones asíncronas, la performance puede mejorar,  pero si accedes cientos de veces por segundo...  estamos en lo mismo. No existe la posibilidad de montar un ciclo de cada X? 

    Por ejemplo, lanzar un thread con algo asi:

    void TuThread() 
    {
        m_bSeguir = true; //Variable miembro del formulario
        int nCycle = 0;
        while(m_bSeguir) //variable bool que pones a false en el formclosing para salir del thread
        { 
           nCycle++;
            //tu logica
            if(nCycle==5)//Aqui los ciclos que consideres
            {
                nCycle=0;
                txtBox.InvokeAsync(new MethodInvoker(delegate{/*que le haces al textbox */})) ;
            } 
        } 
    } 
    Atte


    No olvides votar si mi comentario te ha ayudado y marcarlo como respuesta si ha sido la solución!


    • Editado Jorge TurradoMVP miércoles, 13 de septiembre de 2017 5:01
    • Marcado como respuesta StringCGE jueves, 14 de septiembre de 2017 0:41
    miércoles, 13 de septiembre de 2017 5:00

Todas las respuestas

  • Buenas compañero, 

    El tema es que el aspecto puramente visual,  lo va a seguir haciendo el main thread,  ya que GDI trabaja la interfaz con un solo hilo, cuando haces invocaciones desde otros,  va a ser el main el que realice el acceso y modificación del control. 

    Si gestionas toda la lógica en otro thread y simplemente vas lanzando invocaciones asíncronas, la performance puede mejorar,  pero si accedes cientos de veces por segundo...  estamos en lo mismo. No existe la posibilidad de montar un ciclo de cada X? 

    Por ejemplo, lanzar un thread con algo asi:

    void TuThread() 
    {
        m_bSeguir = true; //Variable miembro del formulario
        int nCycle = 0;
        while(m_bSeguir) //variable bool que pones a false en el formclosing para salir del thread
        { 
           nCycle++;
            //tu logica
            if(nCycle==5)//Aqui los ciclos que consideres
            {
                nCycle=0;
                txtBox.InvokeAsync(new MethodInvoker(delegate{/*que le haces al textbox */})) ;
            } 
        } 
    } 
    Atte


    No olvides votar si mi comentario te ha ayudado y marcarlo como respuesta si ha sido la solución!


    • Editado Jorge TurradoMVP miércoles, 13 de septiembre de 2017 5:01
    • Marcado como respuesta StringCGE jueves, 14 de septiembre de 2017 0:41
    miércoles, 13 de septiembre de 2017 5:00
  • Aun no puedo saber que tanto me funciona ya que estoy trabando en el lenguaje Visual Basic, soy novato en esto y no entiendo del todo los delegados.

    Esto es lo que tengo.

    Public Class Class1 Public Class Form1 Dim Var1 As String Dim Var_Limpiar As Boolean 'Declaracion para el uso del delegado' Public Delegate Sub CGE_Tipo_De_Delegado(myString As String) 'Se declara el tipo de delegado' Public StringCGE_Delegado As CGE_Tipo_De_Delegado 'Se declara el delegado' Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load 'Agregando metodo para el delegado' StringCGE_Delegado = New CGE_Tipo_De_Delegado(AddressOf Metodo_Del_Delegado) If BGW_Incrementa.IsBusy <> True Then BGW_Incrementa.RunWorkerAsync() End If If BackgroundWorker1.IsBusy <> True Then BackgroundWorker1.RunWorkerAsync() End If Timer1.Enabled = True End Sub 'Metodo del delegado' Public Sub Metodo_Del_Delegado(myString As String) StringCGE_TextBox.Text = CGE_TextBox.Text & myString End Sub 'Usando el delegado' Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork While True StringCGE_TextBox.InvokeAsync(CGE_Delegado, New Object() {"Algo"})'Esto me da error'

    'El error es InvokeAsync no es miembro de TextBox' 'StringCGE_TextBox.Invoke(CGE_Delegado, New Object() {"Algo"})' End While End Sub Private Sub BGW_Incrementa_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BGW_Incrementa.DoWork While True If Var_Limpiar Then Var_Limpiar = False Var1 = "" End If Var1 += "a" End While End Sub Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick Var_Tamaño_de_variable.Text = Var1.Length End Sub Private Sub Limpiar_Click(sender As Object, e As EventArgs) Handles Limpiar.Click Var_Limpiar = True Var_Tamaño_de_variable.Text = Var1.Length End Sub End Class End ClassEn

    en

    BackgroundWorker1_DoWork

    tengo mi delegado pero no se aun como usar 

    txtBox.InvokeAsync(new MethodInvoker(delegate{/*que le haces al textbox */})) ;

     en mi aplicación.

    ademas tengo comentadas las lineas referentes al delegado y quisiera que me corrigiera si entendí mal alguna linea por la descripción errónea que pude haber puesto.

    Ademas. Mi intención es hacer algo parecido al monitor serial que se encuentra en el IDE de arduino que por mas que se llena el textBox del monitor serial, la aplicación sigue fluida.

    miércoles, 13 de septiembre de 2017 14:59
  • Buenas, 

    La finalidad de esto es leer un puerto COM? 

    Digo porque si es así,  mejor que todo esto puede ser usar el evento DataRecived de la propia clase. 

    Por otro lado,  no se en que estaba pensando,  el método es BeginInvoke (No InvokeAsync) 

    Lo puedes usar asi

    NombreControl.BeginInvoke(New MethodInvoker(Sub() NombreControl.Text = "he cambiado el texto"))

    Atte


    No olvides votar si mi comentario te ha ayudado y marcarlo como respuesta si ha sido la solución!


    miércoles, 13 de septiembre de 2017 15:02
  • Claro que estoy usando DataRecived y los respectivos try catch donde puede surgir inconvenientes.
    Ya probé con BeginInvoke en el código pero al cabo de un minuto veinte segundos sale

    System.OutOfMemoryException

    Ocupando un poco mas de 4 gigas.

    Pero cuando uso invoke en el mismo periodo de tiempo solo consume 45 megas.

    Ahora he descubierto que el cuello de botella esta en el TextBox porque por cada actualizacion del mismo hago esto

    StringCGE_TextBox.Invoke(CGE_Delegado, New Object() {"Algo"})

    que lleba a esto

    Public Sub Metodo_Del_Delegado(myString As String) StringCGE_TextBox.Text = CGE_TextBox.Text & myString End Sub

    dando como resultado que la aplicacion este congelada

    pero si hago esto

    StringCGE_TextBox.Invoke(CGE_Delegado, New Object() {"Algo"})
    
    Public Sub Metodo_Del_Delegado(myString As String)
                StringCGE_TextBox.Text = myString
            End Sub

    La aplicacion corre casi fluida y con picos maximos de 150 MB y una media de 40MB de consumo de memoria
    Lo que he descubierto es que:

    La aplicacion se cuelga cuando el textBox tiene bastante que actualizar.

    La frecuencia de actualizacion del textBox es bien alta.

    Otro problema que tengo con el text box se da por la posicion del cursor en el textBox.

    Este siempre pasa al principio y luego se coloca en el final generando un incomodo parpadeo usando este codigo

    Cursor_Position = TB_Recibe.Text.Length
    TB_Recibe.Select(Cursor_Position, 0)
    TB_Recibe.ScrollToCaret()

    Tendra solucion en visual basic esto.

    Se puede hacer uso de otra cosa distinta en programacion al textBox pero de similar visualizacion.

    Disculpa si es que incomodo por las preguntas que surjen.

    jueves, 14 de septiembre de 2017 1:12
  • Vale,  puede ser que el problema sea que intentas actualizar demasiado y demasiadas veces el valor del textbox, lo que se me ocurre que puedes hacer,  es en vez de operar el textbox desde el DataRecived,  en el DataRecived almacena el valor en una variable miembro,  y vete actualizando el textbox con un  timer,  pero claro,  al timer dale por lo menos un intervalo de 50ms(20refrescos por segundo esta bien,  igual es incluso demadiado) 

    Atte


    No olvides votar si mi comentario te ha ayudado y marcarlo como respuesta si ha sido la solución!

    jueves, 14 de septiembre de 2017 3:50
  • En otra aplicacion, el textBox se actualiza a 1000ms/60ftp que da 16.66666 y el valor en el timer que uso es de 15ms y el problema persiste pero como dije antes. El cuello de botella es el textBox y este se genera con la cantidad de dados que hay en el. ahora no se como hacer que se quede el cursor en la ultima posición seleccionada sin que parpadee.
    jueves, 14 de septiembre de 2017 12:47
  • Ya esta solucionado esto.
    Rich TextBox es quien mejor se adapta a esta necesidad ya que puede manejar mas datos sin congelar la aplicación y es lo que creo que usa el IDE de arduino en su monitor serial.

    Gracias a Jorge Turrado  fue una buena ayuda muy pronto estaré preguntando mas cosas.

    miércoles, 20 de septiembre de 2017 14:29