none
Como evitar el error Cross-thread operation not valid: Control 'xxxx' accessed from a thread other than the... RRS feed

  • Pregunta

  • Hola necesito ayuda !!!

    En mi aplicacion realizo una consulta a una BD y muestro los datos deseados. Pero en una columna tipo image muestro una paloma verde si existe un PDF y una cruz roja si no existe (el nombre del PDF se vasa en una columna llamada 'PDF').  Los Pdfs los tengo en una carpeta compartida en la red. Bueno el asunto es que utilizo un BackgroundWorker para lograr mostrar un RadWaitingBar(es como un progressbar) mientras se realiza esa consulta en la BD.

    Pero en una parte de mi codigo me sale el error(Me sale cuando le asigno un datatable a mi grid): "Cross-thread operation not valid: Control 'Grid' accessed from a thread other than the..."

    Mi codigo mas o menos esta asi:

    Private Sub AceptarBtn_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles AceptarBtn.Click
            Try
                If Me.ClienteTxt.Text <> "" Then
                    MDIForm.ProcesandoElement.Visibility = ElementVisibility.Visible
                    MDIForm.WaitingBar1.Visible = True
                    MDIForm.WaitingBar1.StartWaiting() //Empieza a correr el waitingbar

                    BackgroundWorker1.RunWorkerAsync()

                Else
                  //mensaje de alerta
                End If
            Catch ex As Exception
                //mensaje de error
            End Try
        End Sub

      Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
            Consulta2(Me.ClienteTxt.Text.Trim)  //ejecuto este procedimineto
        End Sub

       Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As System.Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
            MDIForm.RadWaitingBar1.EndWaiting() //detengo y desabilito el waitingbar
            MDIForm.RadWaitingBar1.Visible = False
            MDIForm.ProcesandoElement.Visibility = ElementVisibility.Hidden
        End Sub

        Private Sub Consulta2(ByVal Cliente As String)

            Dim sSel As String = "  //// mi consulta, utilizando variable cliente como mi ID ///// "

            Dim da As SqlDataAdapter
            Dim dt As DataTable = New DataTable
            da = New SqlDataAdapter(sSel, CON)
            da.SelectCommand.CommandTimeout = 300
            da.Fill(dt)

            If dt.Rows.Count > 0 Then
                Dim tablas As DataTable = validar(dt) //utilizo esta funcion para verificar si existe los PDFs correspondientes
                Me.GridView1.DataSource = tablas   [[[Aqui me sale el error]]]
            Else
             // Mensaje de alerta
            End If

        End Sub

    //Con esta función asigno una unidad ('P') a la carpeta compartida, esto lo hago asi para que no me pregunte siempre el usuario y password//
     Private Function validar(ByVal tabla As DataTable) As DataTable
            Dim Col As DataColumn = tabla.Columns.Add("PDFs", Type.GetType("System.String"))
            Col.AllowDBNull = True

            Dim myProcess As New Process()
            Dim myProcessStartInfo As New ProcessStartInfo("net ", "use " + "p: \\"IP"\"CARPETA COMPARTIDA" /USER:"Dominio"\"Usuario" Password")

            myProcessStartInfo.UseShellExecute = False
            myProcessStartInfo.RedirectStandardError = True
            myProcess.StartInfo = myProcessStartInfo
            myProcess.Start()
            myProcess.WaitForExit()

    //Aqui en estas partes verifico si existe el PDF correspondiente
            Dim ruta As String = "\\xxx.xxx.xxx.x\pdf\"
            If Directory.Exists(ruta) Then

                For Each row As DataRow In tabla.Rows
                    If Not row("PDF") Is DBNull.Value And Not row("PDF") Is Nothing Then
                        If File.Exists("\\xxx.xxx.xxx.x\pdf\NLA\" & CStr(row("PDF")) & ".pdf ") Then
                            row("PDFs") = "Si"
                        Else
                            row("PDFs") = "No"
                        End If
                    End If
                Next

            End If

            Dim myProcess2 As New Process()
            Dim myProcessStartInfo2 As New ProcessStartInfo("net ", "use " + "p: /delete")

            myProcessStartInfo2.UseShellExecute = False
            myProcessStartInfo2.RedirectStandardError = True
            myProcess2.StartInfo = myProcessStartInfo2
            myProcess2.Start()
            myProcess2.WaitForExit()
            Return tabla
        End Function

    La aplicación me hace todo bien pero me muestra ese molesto mensaje verde. Y no he podido resolver esto. 

    Por favor necesito de su sabiduria T_____T !!!!!

    martes, 10 de mayo de 2011 21:59

Respuestas

  • Gracias a todos por su ayuda.

    Lo resolvi con el siguiente código:

    1.- Asigno un delegado

        Delegate Sub SetGridDatasource_Delegate(ByVal Grid As DataGridView, ByVal DataSource As DataTable)

        Private Sub SetGridDatasource_ThreadSafe(ByVal Grid As DataGridView, ByVal DataSource As DataTable)

            If Grid.InvokeRequired Then
                Dim MyDelegate As New SetGridDatasource_Delegate(AddressOf SetGridDatasource_ThreadSafe)
                Me.Invoke(MyDelegate, New Object() {Grid, DataSource})
            Else
                Grid.DataSource = DataSource
            End If

        End Sub


    2.- Llamo a la funcion

    SetGridDatasource_ThreadSafe(Me.GridView1, tablas) 

    Y listo !!!!    ^^

    Saludos.

    • Marcado como respuesta Julio Espinosa miércoles, 11 de mayo de 2011 14:41
    miércoles, 11 de mayo de 2011 14:41
  • El problema es que estás modiicando un objeto de la interfaz de usuario (el progressbar) desde el Worker Process del BackgoundWorker. La interfaz de usuario en Windows Forms es monotarea y no se puede moficar desde otro hilo que no sea el que la creó. Para ello, es necesario transportar la ejecución desde el hilo secundario al hilo principal antes de modificar estos objetos. Cuando el hilo lo creas tú mismo, lo más habitual es realizar el transporte entre hilos mediante el método Invoke. Pero el BackgroundWorker tiene ya previsto un mecanismo expreso para esta finalidad: Se trata del método ReportProgress, que dispara un evento ProgressChanged en el hilo principal, y es en este evento donde debes modificar el ProgressBar. Examina la documentación del BackgroundWorker, esto está bien documentado y hay muchos ejemplos.
    miércoles, 11 de mayo de 2011 5:37
  • :-)
    Hola,

    Como bien dice Alberto, una característica de seguridad del .NET Framework es que no permite actualizar la interfaz de usuario desde un thread que no sea el principal. Si has creado tu el thread puedes usar Control.Invoke.

    En tu caso (BackgroundWorker) haz lo que te dice Alberto, usa el evento ProgressChanged, ya que desde éste si puedes actualizar la interfaz.

    Saludos,


    No olvides marcar la respuesta como correcta si te ha sido de utilidad :-)

    [MS-MVP-MCTS]

    Follow me on Facebook or Twitter!

    Mi Perfil MVP en: https://mvp.support.microsoft.com/profile/Lluis
    NUG: http://andorradotnet.com
    Web: http://www.ordeeno.com
    Geeks: http://geeks.ms/blogs/lfranco

    • Marcado como respuesta Julio Espinosa miércoles, 11 de mayo de 2011 14:41
    miércoles, 11 de mayo de 2011 9:09
    Moderador

Todas las respuestas

  • El problema es que estás modiicando un objeto de la interfaz de usuario (el progressbar) desde el Worker Process del BackgoundWorker. La interfaz de usuario en Windows Forms es monotarea y no se puede moficar desde otro hilo que no sea el que la creó. Para ello, es necesario transportar la ejecución desde el hilo secundario al hilo principal antes de modificar estos objetos. Cuando el hilo lo creas tú mismo, lo más habitual es realizar el transporte entre hilos mediante el método Invoke. Pero el BackgroundWorker tiene ya previsto un mecanismo expreso para esta finalidad: Se trata del método ReportProgress, que dispara un evento ProgressChanged en el hilo principal, y es en este evento donde debes modificar el ProgressBar. Examina la documentación del BackgroundWorker, esto está bien documentado y hay muchos ejemplos.
    miércoles, 11 de mayo de 2011 5:37
  • :-)
    Hola,

    Como bien dice Alberto, una característica de seguridad del .NET Framework es que no permite actualizar la interfaz de usuario desde un thread que no sea el principal. Si has creado tu el thread puedes usar Control.Invoke.

    En tu caso (BackgroundWorker) haz lo que te dice Alberto, usa el evento ProgressChanged, ya que desde éste si puedes actualizar la interfaz.

    Saludos,


    No olvides marcar la respuesta como correcta si te ha sido de utilidad :-)

    [MS-MVP-MCTS]

    Follow me on Facebook or Twitter!

    Mi Perfil MVP en: https://mvp.support.microsoft.com/profile/Lluis
    NUG: http://andorradotnet.com
    Web: http://www.ordeeno.com
    Geeks: http://geeks.ms/blogs/lfranco

    • Marcado como respuesta Julio Espinosa miércoles, 11 de mayo de 2011 14:41
    miércoles, 11 de mayo de 2011 9:09
    Moderador
  • Primeramente agradesco que me hallan respondido.

    Analisando mi codigo,  en el evento "BackgroundWorker1_DoWork" nunca modifico el progressbar. Eso lo hago en el evento "BackgroundWorker1_RunWorkerCompleted".

    El el evento DoWork ejecuto una función, y en una linea de codigo de esa funcion es donde me sale el error.  Es en la parte donde al gridview le asigno el resultado de un procedimineto:

    A un datatable le asigno el resultado de un procedimineto, el cual me trae un datatable. Y a el gridview le asigno como Datasource ese datatable

    Dim tablas As DataTable = validar(dt)  //En la funcion 'validar' realizo ciertas acciones y regreso un Datatable  
    Me.GridView1.DataSource = tablas   <-----  Aqui es donde me sale el error.

    He leido información sobre lo que me han explicado, de usar el metodo invoke, de usar un delegado, entre otras cosas, Tambien sobre lo incorrecto de asignar un valor a un control de la UI desde otro hilo.

    Pero no entiendo muy bien eso, soy nuevo en todo esto de los hilos T____T.

    Todos los ejemplos que veo en la web son acerca de un textbox. ¿Como podria hacerle en el caso de un gridview?

    Saludos.

     

    miércoles, 11 de mayo de 2011 14:21
  • Al fin pude .....  =)  

    Gracias a todos los que respondieron a la llamada de auxilio O.^ !!!

    Por los que quieran saber cual fue la solucion, aqui se las pongo:

    1.- creo un delegado.

        Delegate Sub SetGridDatasource_Delegate(ByVal Grid As DataGridView, ByVal DataSource As DataTable)

        Private Sub SetGridDatasource_ThreadSafe(ByVal Grid As DataGridView, ByVal DataSource As DataTable)

            If Grid.InvokeRequired Then
                Dim MyDelegate As New SetGridDatasource_Delegate(AddressOf SetGridDatasource_ThreadSafe)
                Me.Invoke(MyDelegate, New Object() {Grid, DataSource})
            Else
                Grid.DataSource = DataSource
            End If

        End Sub

    2.- Despues ponemos lo siguiente en el evento DoWork

    SetGridDatasource_ThreadSafe(Me.GridView1, tablas) 

    Y listo !!!!!   ^^

    Saludos.

    miércoles, 11 de mayo de 2011 14:37
  • Gracias a todos por su ayuda.

    Lo resolvi con el siguiente código:

    1.- Asigno un delegado

        Delegate Sub SetGridDatasource_Delegate(ByVal Grid As DataGridView, ByVal DataSource As DataTable)

        Private Sub SetGridDatasource_ThreadSafe(ByVal Grid As DataGridView, ByVal DataSource As DataTable)

            If Grid.InvokeRequired Then
                Dim MyDelegate As New SetGridDatasource_Delegate(AddressOf SetGridDatasource_ThreadSafe)
                Me.Invoke(MyDelegate, New Object() {Grid, DataSource})
            Else
                Grid.DataSource = DataSource
            End If

        End Sub


    2.- Llamo a la funcion

    SetGridDatasource_ThreadSafe(Me.GridView1, tablas) 

    Y listo !!!!    ^^

    Saludos.

    • Marcado como respuesta Julio Espinosa miércoles, 11 de mayo de 2011 14:41
    miércoles, 11 de mayo de 2011 14:41
  • :-)
    Hola,

    Genial! Me alegro que lo hayas podido solucionar.

    Por cierto, si usas el .NET framework 4.0, puedes usar la clase Task, que simplifica mucho el uso de hilos:

    var t = Task.Factory.StartNew(() => DoAction());
    

    StartNew recibe un parámetro de tipo Action, siendo DoAction el método a ejecutar.

    Saludos,


    No olvides marcar la respuesta como correcta si te ha sido de utilidad :-)

    [MS-MVP-MCTS]

    Follow me on Facebook or Twitter!

    Mi Perfil MVP en: https://mvp.support.microsoft.com/profile/Lluis
    NUG: http://andorradotnet.com
    Web: http://www.ordeeno.com
    Geeks: http://geeks.ms/blogs/lfranco

    miércoles, 11 de mayo de 2011 15:14
    Moderador
  • muchisimas gracias!!! Me re sirvió con dos controles, un datagridview y un combobox (hice una pequeña modificacion en la funcion para poner el valuemember y displaymember

    Private Sub SetGridDatasource_ThreadSafe1(ByVal combo As ComboBox, ByVal DataSource As DataTable) If combo.InvokeRequired Then            Dim MyDelegate As New SetGridDatasource_Delegate1(AddressOf SetGridDatasource_ThreadSafe1)

    Me.Invoke(MyDelegate, New Object() {combo, DataSource}) 

    Else            combo.DataSource = DataSource            combo.ValueMember = DataSource.Columns(0).Caption.ToString combo.DisplayMember = DataSource.Columns(1).Caption.ToString End If End Sub

    :)
    • Editado thyranus martes, 21 de mayo de 2013 22:25
    martes, 21 de mayo de 2013 22:24