thread multihilos

Iniciado por Juan Sanchez, 2 Noviembre 2017, 15:45 PM

0 Miembros y 2 Visitantes están viendo este tema.

Juan Sanchez

Que tengan un buen dia a todos los integrantes del foro.
Quiero hacerles la consulta, cuando utilizo thread dentro de otro thread se bloquea mi ventana, no se si es bueno aplicar un hilo dentro de otro hilo, si trabajo con un hilo todo bien pero agrego otro hilo dentro de la misma es ahi donde se bloque mi ventana.

Tal ves no tenga sentido pero lo que quiero hacer es cargar de un datatable a listview pero con el primer hilo tarda en cargar datos en el listview y por cada fila al agregar un item en listview se tarda pero la idea es acelerar la carga y la idea que tuve es por cada fila en datatable disparar otro hilo y es ahí donde empieza a bloquear mi ventana, pero la carga de datos a listview es rápido.

la pregunta es si es conveniente utilizar un hilo dentro de otro o hay otra forma de hacer hilos anidados si que me bloquee mi ventana.

El código es lo siguiente para que se me entienda bien...

Gracias de antemano por sus respuestas


Código (vbnet) [Seleccionar]
Imports System.Data.SqlClient
Imports System.Threading
Imports System.ComponentModel
<Microsoft.VisualBasic.ComClass()> <ToolboxBitmap(GetType(System.Windows.Forms.ListView))> <System.Serializable()> Public Class ListView
  Inherits Windows.Forms.ListView
  Dim thSQL As Thread
  Dim _sq As String, _decimales As Integer, _EllipseIcon As Boolean, _bordColor As Pen
  Public Sub SQLConsulta(ByVal sq As String, Optional ByVal EllipseIcon As Boolean = False, Optional ByVal borderColor As Pen = Nothing, Optional ByVal decimales As Integer = 0)
    _sq = sq : _decimales = decimales : _EllipseIcon = EllipseIcon : _bordColor = borderColor
    thSQL = New Thread(AddressOf SQLConsultaInterno)
    thSQL.Start()
  End Sub
  Private Sub SQLConsultaInterno()
    Try
      Dim h As Thread
      Dim conexion As SqlConnection = New SqlConnection()
      conexion.ConnectionString = "Data Source=ip_server,1433;Failover Partner=ip_server2,1433;Network Library=DBMSSOCN;MultipleActiveResultSets=true;Initial Catalog=DB;User ID=sa;Password=contraseña;" : conexion.Open()
      Dim ds As New DataSet(), da As New SqlDataAdapter(_sq, conexion) : da.Fill(ds, "tabla")
      If ds.Tables("tabla").Columns.Count = 1 Then
        For c As Integer = 0 To ds.Tables("tabla").Columns.Count - 1
          AddColumn(ds.Tables("tabla").Columns(c).Caption)
        Next
      Else
        For c As Integer = 1 To ds.Tables("tabla").Columns.Count - 1
          AddColumn(ds.Tables("tabla").Columns(c).Caption)
        Next
      End If
      With ds.Tables("tabla")
        For filas As Integer = 0 To .Rows.Count - 1
          Dim ob As New ROW
          ob.fila = .Rows(filas)
          ob.columnas = .Columns.Count
          ob.index = filas
          h = New Thread(AddressOf agregar)
          h.Start(ob)
        Next
      End With
      conexion.Close()
    Catch ex As Exception

    End Try
  End Sub
  Private Sub agregar(obj As Object)
    Dim rw As ROW = DirectCast(obj, ROW)
    If rw.columnas = 1 Then
      Dim Registro As New ListViewItem(rw.fila.Item(0).ToString.Trim, rw.index)
      AddItem(Registro)
    Else
      Dim Registro As New ListViewItem(rw.fila.Item(1).ToString.Trim, rw.index)
      Registro.Tag = rw.fila.Item(0).ToString.Trim
      For c As Integer = 2 To rw.columnas - 1
        Registro.SubItems.Add(rw.fila.Item(c).ToString().Trim)
      Next
      AddItem(Registro)
    End If
  End Sub

  Delegate Sub AddColumns(ByVal texto As String)
  Private Sub AddColumn(ByVal texto As String)
    If Me.InvokeRequired Then
      Dim d As New AddColumns(AddressOf AddColumn)
      Invoke(d, New Object() {texto})
    Else
      Me.Columns.Add(texto)
    End If
  End Sub
  Delegate Sub AddItems(ByVal item As ListViewItem)
  Private Sub AddItem(ByVal item As ListViewItem)
    If Me.InvokeRequired Then
      Dim d As New AddItems(AddressOf AddItem)
      Invoke(d, New Object() {item})
    Else
      Me.Items.Add(item)
    End If
  End Sub

  Private Class ROW
    Property fila As DataRow
    Property columnas As Integer
    Property index As Integer
  End Class
End Class

Serapis

#1
De entrada el tema, no debiera haberse puesto aquí. este hilo es para VB6.0 y versiones previas, el adecuado es en .net: https://foro.elhacker.net/net-b62.0/

El tema d ela actualización de vistas, ya se ha tratado.
Se ralentiza cuando un control tras añadir un ítem, trata de actualizar la vsta... entonces si en vez de querer añadir 1 sólo ítem se pretende añadir cientos o miles, el control debe repintarse cientos o miles de veces, de ahí la lentitud...

La solución?, Hay varias...
1 - La más sencilla es ver si el control admite un método AddRange(items), con lo que puedes pasarle un array o colección de algún tipo... (ver sobrecargas).

2 - Otra opción en NET (cuando programas controles del propio NET) es usar los métodos:
   objeto.BeginUpdate
   objeto.DataSource = elArray
   objeto.EndUpdate

...cuya misión es precisamente evitar el redibujado. BeginUpdate y EndUpdate marcan el inicio y fin del 'NO-redibujado'...

3 - La práctica cuando el objeto no admite ninguna opción al respecto:
  objeto.visible =false
  bucle desde 0 hasta array.count ' o el tope del objeto que sea.
     objeto.addItem(array(k))
  fin bucle
  objeto.visible=true

De hecho este mismo código puede ser parte de una función que tú crees llamada precisamente AddRange(coleccion de algún tipo)

De este modo, fuerzas a que no se redibuje con cada añadido. El propio control (salvo que esté muy mal programado), cuando se añade un ítem no intentará redibujar si resulta que el control no está visible, en consecuencia no pierde tiempo en redibujarlo x veces, si no sólo una al final, cuando lo haces visible de nuevo.



Entonces te darás cuenta que no necesitas múltiples hilos para actualizar un control, de hecho no es aconsejable, porque se pelearán (competirán) por el acceso al control. Los hilos múltiples son adecuados para trareas asíncronas, y esta es una tarea síncrona.



P.d.:
OJO: El pseudocódigo:
  objeto.visible =false
   bucle desde 0 hasta array.count ' o el tope del objeto que sea.
      objeto.addItem(array(k))
   fin bucle
   objeto.visible=true

No considera el caso de que el objeto esté invisible, en realidad si fue ocultado desde otro lado, aquí no debería hacerse visible.
 
   Buleano v = objeto.visible
   objeto.visible =false
   bucle desde 0 hasta array.count ' o el tope del objeto que sea.
      objeto.addItem(array(k))
   fin bucle
   objeto.visible=v ' esto es, reasignamos el mismo estado que tenía previamente

Eleкtro

#2
La solución óptima ya te la ha explicado el compañero @NEBIRE, así que me centraré en puntualizar algunas otras cosas mejorables y que son bastante importantes:

1. La próxima vez, y siempre que las circunstancias sean las correctas, en lugar de optar por el uso de la clase System.Threading.Thread deberías utilizar la clase System.ComponentModel.BackgroundWorker, ya que está especificamente diseñada para simplificar y optimizar la interacción con la UI mediante su modelo de clase orientado a eventos.

Para todo lo demás, en lugar de la clase System.Threading.Thread deberías recurrir a la clase System.Threading.Tasks.task, o hacer uso de las palabras reservadas Async y Await. En general utilizar la clase Thread es una solución poco óptima si no existe una necesidad real de crear un Thread de forma explícita, como sería por ejemplo para crear y configurar opciones por thread (sería algo similar a configurar opciones por aplicación u opciones por usuario), o para crear un Thread persistente (durante el tiempo de ejecución de la aplicación) que necesite ser identificado cada cierto tiempo por el motivo que fuese...

2. Siempre que una clase no tenga la necesidad de ser heredable, como es el caso de la clase que definiste en tu código con nombre "Row", deberías asignarle el modificador NotInheritable a la firma del miembro para obtener una micro-optimización en la instanciación del tipo y por ende una micro-optimización más notable en la carga de datos de varios elementos del mismo tipo al representarlos en un control.

3. En tu clase o control de usuario "ListView" estás utilizando tipos que implementan la interfáz IDisposable como es el caso de la clase System.Drawing.Pen, al instanciar este tipo de clases generan recursos no administrados (nativos) que deben ser liberados mediante el método IDisposable.Dispose() ( Pen.Dispose() ), así que deberías sustituir la implementación heredada de la interfáz IDisposable para completar su funcionalidad liberando esos nuevos recursos no heredados que estás utilizando en tu código...

En este caso en específico con objetos de tipo Pen y SolidBrush y etc. se debe tener en cuenta que existe un límite máximo de recursos de GDI que un proceso puede mantener abiertos (el cual está especificado en el registro de Windows, como muchas otros límites varios), aunque dicho límite es exageradamente elevado y eso lo hace casi "inalcanzable" por una aplicación de consumo general... claro está a menos que los recursos se administren mal, si generamos varias decenas de miles de recursos nativos GDI en un intervalo corto de tiempo, un intervalo más corto del que el GC puede recojer más recursos a tiempo de los que se generan, y rebasar dicho límite.

Lo que quiero decir es que... el método Dispose está para algo, existe para ser utilizado, para ayudar al GC en la liberación de dichos recursos en el intervalo más corto posible de tiempo, no para ignorarlo y dejar que el GC empiece la liberación cuando le de la gana (o que al final no lo haga), así que dale uso al método Dispose, este mensaje va dirigido a todo el mundo/programador de .NET que lo lea, que estoy bastante mosqueado de ver siempre como personas "aprenden a programar en .NET" sin aprender lo básico y fundamental para elaborar un programa estable y sin fugas, pues no liberar los recursos de un objeto cuando ya no se necesitan ser utilizados es algo que se considera una de las peores y más horribles prácticas en la programación orientada a objetos.

Ejemplo:

Código (vbnet) [Seleccionar]
Public Class MyListView : Inherits ListView

   Private MyPen As Pen

   Public Sub New()
       Me.MyPen = New Pen(Color.Empty)
   End Sub

   Protected Overrides Sub Dispose(disposing As Boolean)
       If (disposing) Then
           ' Aquí llamas al método Pen.Dispose()
           ' y adicionálmente anulas la referencia
           ' para optimizar (acortar) el intervalo de tiempo
           ' en el que efectuará el GarbageCollector.
           Me.MyPen.Dispose()
           Me.MyPen = Nothing
       End If

       ' Aquí llamas al método base Dispose() de la herencia para liberar los
       ' recursos por defecto utilizados por la clase ListView.
       MyBase.Dispose(disposing)
   End Sub

End Class


Saludos.








Juan Sanchez

Bueno agradecerles a nebire y elektro por sus conceptos bien puntualizados, sobre todo en la administración de recursos de un objeto.
Les agradezco mucho por haberme despejado dudas sobre programación en net.