Pos no me he rendido con los THREADS

04/01/2006 - 20:47 por Ch0rY | Informe spam
rendirme no es lo mio, cansarme si, pero rendirme no.

Asi q estoy de vuelta con el tema

Si recordais, tenia intencion de hacer unos threads para "operar" con
varios archivos o varias filas de un listview a la vez.

Al principio no fui capaz de crear un simple thread, hoy lo he conseguido y
he sido capaz de crear una clase q lo use.

Pero no se como hacer q se ejecute 4 veces simultaneamente

Este es el proyecto q he creado

En un form, metemos un listview llamado lst_View y cuatro botones
(btn_h1,btn_h2,btn_h3,btn_h4)

Este es el codigo en el form

Imports System.Threading

Public Class Form1
Dim Hilin(3) As Pilla
Private Sub Form1_FormClosed(ByVal sender As Object, ByVal e As
System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed
Me.Dispose()
End
End Sub

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
Dim Contador As Byte
Dim lvwItem As ListViewItem
For Contador = 1 To 254
lvwItem = New ListViewItem(Contador.ToString)
lvwItem.SubItems.Add("")
lst_View.Items.Add(lvwItem)
Next
End Sub

Private Sub btn_h1.Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btn_h1.Click, btn_h2.Click, btn_h3.Click,
btn_h4.Click
Dim Contador As Byte
For Contador = 0 To 4
If IsNothing(Hilin(Contador)) Then
Hilin(Contador) = New Pilla
If Not Hilin(Contador).Iniciado Then
Hilin(Contador).lista = Me.lst_View
Hilin(Contador).Iniciar()
Exit Sub
End If
End If
Next
End Sub
End Class

y esta es la clase que he creado (PILLA)

Imports System.Threading

Public Class Pilla
Delegate Sub Hazlo()
Public ElHilo As Thread = Nothing
Public lista As ListView

Private _Iniciado As Boolean

ReadOnly Property Iniciado() As Boolean
Get
Iniciado = _Iniciado
End Get
End Property

Public Sub Hilo()
Dim lvwItem As ListViewItem
_Iniciado = True
If lista.InvokeRequired Then
Dim Hilo2 As New Hazlo(AddressOf Hilo)
lista.Invoke(Hilo2)
Else
For Each lvwItem In lista.Items
If lvwItem.Checked Then Exit For
lvwItem.Checked = True
Dim Contador As Integer
For Contador = 0 To 65000
Application.DoEvents()
lvwItem.SubItems(1).Text = Format("0.00", Contador).ToString
Next
Next
End If
_Iniciado = False
End Sub

Public Sub Iniciar()
ElHilo = New Thread(New ThreadStart(AddressOf Hilo))
ElHilo.Start()
End Sub

End Class


Me gustaria q lo echaseis un vistazo y si no podeis decirme como hacer lo
que quiero, si me gustaria me dijerais, q os parece, q fallos veis q estoy
cometiendo, y tal y tal...


Gracias de nuevo.

Reconocereis q como cura no tendria precio, tol dia pidiendo :D
 

Leer las respuestas

#1 Jesús López
05/01/2006 - 13:14 | Informe spam
Bueno, en realidad creo que no lo estás enfocando bien. La cuestión no es
actualizar un listview usando varios hilos, sino hacer una o varias tareas
en segundo plano usando hilos e ir actualizando el listview en función del
progreso o terminación de esas tareas. De forma más genérica lo diríamos
así: la aplicación realiza una o varias tareas en segundo plano y va
actualizando el interfaz del usuario según se van realizando dichas tareas.
Uno de los fines de este diseño es que el interfaz del usuario no se quede
congelado o aparentemente colgado mientras se realizan tareas de
relativamente larga duración. Otro de los fines es tardar menos tiempo en
realizar varias tareas si esas tareas pueden hacerse en paralelo.

Uno de los "fallos" que tiene el código que has puesto aquí, es que en
realidad prácticamente nada se está ejecutando en los hilos que creas. El
método que es el punto de entrada de los hilos:

Public Sub Hilo()
Dim lvwItem As ListViewItem
_Iniciado = True
If lista.InvokeRequired Then
Dim Hilo2 As New Hazlo(AddressOf Hilo)
lista.Invoke(Hilo2)
Else
For Each lvwItem In lista.Items
If lvwItem.Checked Then Exit For
lvwItem.Checked = True
Dim Contador As Integer
For Contador = 0 To 100
Application.DoEvents()
lvwItem.SubItems(1).Text = Format("0.00",
Contador).ToString
Next
Next
End If
_Iniciado = False
End Sub

Lista.InvokeRequired siempre va a devolver True la primera vez, ya que Sub
Hilo() se está ejecutando en un hilo diferente del que creó el listview. Por
tanto se ejecutará lista.Invoke(Hilo2) lo que provoca que se ejecute Sub
Hilo() en el hilo que creó el listview o sea el hilo principal de la
aplicación, no el hilo que has creado. En consecuencia la gran parte del
trabajo (lo que hay en el Else) se está ejecutando en el hilo principal. Por
eso has tenido que poner DoEvents, si no, la aplicación parecería colgada.
Esto es así porque en realidad el trabajo no se está ejecutando en segundo
plano sino en primer plano.

Otras cosas que me "chocan" en el código es que tengas cuatro botones que
hacen exáctamente lo mismo, con uno habría bastado.


Hay dos razones por las que no se ejecutan las cosas cuatro veces al mismo
tiempo:

(1) Lo que te he dicho de que en realidad tus hilos no hacen nada, sino que
el trabajo se ejecuta en el hilo principal.
(2) La línea de código If lvwItem.Checked then Exit For. Una vez hecho algo
con un item ya no se vuelve a hacer nada.


Mi recomendación es que hagas tres cosas:

(1) Leas la documentación
(2) Leas la documentación
(3) Leas la documentación

Creo que deberías empezar por lo más sencillo que es el componente
Background Worker:

http://msdn2.microsoft.com/en-us/library/c8dcext2(en-US,VS.80).aspx

Depués deberías seguir con los patrones de diseño en programación asíncrona:

http://msdn2.microsoft.com/en-us/library/ms228969(en-US,VS.80).aspx

Y bueno, aquí tienes un ejemplo de implementación del patrón asíncrono
basado en eventos, en el que utilizo hilos en vez de la clase
AsyncOperationManager que sugiere la documentación. La implementación que he
hecho del patrón no es muy estricta, pero sigue su esencia.

Este ejemplo se basa en una clase ContactsLoader que va obteniendo todos los
contactos de la tabla Person.Contact de la base de datos AdventureWorks:
http://www.microsoft.com/downloads/...x?FamilyIDç19ecf7-9f46-4312-af89-6ad8702e4e6e&DisplayLang=en

ContacstLoader abre la conexión, ejecuta la instrucción select y recupera
los datos de la base de datos en segudo plano (un hilo). Cada vez que
recupera un registro lanza un evento informando de ese registro. El
lanzamiento del evento se ejecuta en primer plano para evitar problemas de
sincronización con los controles windows forms. Para eso utiliza
SynchronizationContext.

El ejemplo es un formulario con un botón, un gridview y un progress bar.
Este es el código:

Imports System.Threading

Imports System.ComponentModel



Public Class frmContacts

Dim WithEvents Loader As New ContactsLoader

Dim ContactsList As New BindingList(Of Contact)

Dim ContactsCount As Integer

Dim ContactsLoaded As Integer



Private Sub Loader_ContactRetreived(ByVal sender As Object, ByVal e As
ContactRetreivedEventArgs) Handles Loader.ContactRetreived

ContactsLoaded += 1

Me.ContactsProgressBar.Value = ContactsLoaded * 100 / ContactsCount

ContactsList.Add(e.Contact)

End Sub



Private Sub Loader_OperationCompleted(ByVal sender As Object, ByVal e As
OperationCompletedEventArgs) Handles Loader.OperationCompleted

If e.Success Then

Me.ContactsProgressBar.Visible = False

Else

MsgBox("Error al cargar los contactos: " & vbNewLine & vbNewLine
& e.Exception.Message, MsgBoxStyle.Critical)

End If

Me.LoadContactsButton.Enabled = True

End Sub



Private Sub LoadContactsButton_Click(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles LoadContactsButton.Click

ContactsLoaded = 0

Me.ContactsProgressBar.Visible = True

Me.ContactsProgressBar.Value = 0

Me.LoadContactsButton.Enabled = False

Me.ContactsList.Clear()

Loader.LoadAsync()

End Sub



Private Sub frmContacts_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load

Me.grid.DataSource = Me.ContactsList

Me.ContactsCount = Me.Loader.GetContactsCount()

Me.ContactsProgressBar.Visible = False

End Sub

End Class





Public Class Contact



Private _ContacId As Integer

Public Property ContactId() As Integer

Get

Return _ContacId

End Get

Set(ByVal value As Integer)

_ContacId = value

End Set

End Property





Private _FirstName As String

Public Property FirstName() As String

Get

Return _FirstName

End Get

Set(ByVal value As String)

_FirstName = value

End Set

End Property





Private _LastName As String

Public Property LastName() As String

Get

Return _LastName

End Get

Set(ByVal value As String)

_LastName = value

End Set

End Property





Private _Email As String

Public Property Email() As String

Get

Return _Email

End Get

Set(ByVal value As String)

_Email = value

End Set

End Property



End Class



Public Class ContactRetreivedEventArgs

Inherits EventArgs

Private _Contact As Contact

Public ReadOnly Property Contact() As Contact

Get

Return _Contact

End Get

End Property



Public Sub New(ByVal Contact As Contact)

_Contact = Contact

End Sub

End Class



Public Class OperationCompletedEventArgs

Inherits EventArgs



Private _Success As Boolean

Public ReadOnly Property Success() As Boolean

Get

Return _Success

End Get

End Property





Private _Exception As Exception

Public ReadOnly Property Exception() As Exception

Get

Return _Exception

End Get

End Property



Public Sub New(ByVal Exception As Exception)

_Exception = Exception

_Success = Exception Is Nothing

End Sub



End Class



Public Class ContactsLoader

Public Event ContactRetreived As EventHandler(Of
ContactRetreivedEventArgs)

Public Event OperationCompleted As EventHandler(Of
OperationCompletedEventArgs)



Private Context As SynchronizationContext

Private WorkingThread As Thread

Private OnContactRetreivedCallBack As SendOrPostCallback = New
SendOrPostCallback(AddressOf OnContactRetreived)

Private OnOperationCompletedCallBack As SendOrPostCallback = New
SendOrPostCallback(AddressOf OnOperationCompleted)



Public Sub LoadAsync()

If IsInProgress Then

Throw New InvalidOperationException("No se pueden cargar otra
vez los contactos mientras se están cargando")

End If

Context = SynchronizationContext.Current

WorkingThread = New Thread(AddressOf BackgroundLoad)

WorkingThread.Priority = ThreadPriority.BelowNormal

WorkingThread.Start()

End Sub



Public Sub Cancel()

If IsInProgress Then

WorkingThread.Abort()

End If

End Sub



Private ReadOnly Property IsInProgress() As Boolean

Get

Return WorkingThread IsNot Nothing AndAlso WorkingThread.IsAlive

End Get

End Property



Private Sub BackgroundLoad()

Dim cn As New SqlClient.SqlConnection("Data
Source=(local);Integrated Security=SSPI;Initial Catalog=AdventureWorks")

Dim cmd As New SqlClient.SqlCommand("select ContactID, FirstName,
LastName, EmailAddress from Person.Contact", cn)

Dim reader As SqlClient.SqlDataReader = Nothing

Try

cn.Open()

reader = cmd.ExecuteReader()

While reader.Read()

Dim Contact As New Contact()

With Contact

.ContactId = reader("ContactID")

.FirstName = reader("FirstName")

.LastName = reader("LastName")

.Email = reader("EmailAddress")

End With

Context.Post(OnContactRetreivedCallBack, Contact)

End While

Context.Post(OnOperationCompletedCallBack, Nothing)

Catch ex As ThreadAbortException

If reader IsNot Nothing Then

cmd.Cancel()

End If

Context.Post(OnOperationCompletedCallBack, New
OperationCanceledException("La carga de los contactos ha sido cancelada"))

Catch ex As Exception

Context.Post(OnOperationCompletedCallBack, ex)

Finally

If reader IsNot Nothing Then

reader.Close()

End If

If cn IsNot Nothing AndAlso cn.State <> ConnectionState.Closed
Then

cn.Close()

End If

End Try

End Sub



Public Function GetContactsCount() As Integer

Dim cn As New SqlClient.SqlConnection("Data
Source=(local);Integrated Security=SSPI;Initial Catalog=AdventureWorks")

Dim cmd As New SqlClient.SqlCommand("select count(*) from
Person.Contact", cn)

Try

cn.Open()

Return cmd.ExecuteScalar()

Finally

If cn.State <> ConnectionState.Closed Then cn.Close()

End Try

End Function





Private Sub OnContactRetreived(ByVal Contact As Object)

Dim e As New ContactRetreivedEventArgs(Contact)

RaiseEvent ContactRetreived(Me, e)

End Sub



Private Sub OnOperationCompleted(ByVal Exception As Object)

Dim e As New OperationCompletedEventArgs(Exception)

RaiseEvent OperationCompleted(Me, e)

End Sub



End Class



Que lo disfrutes :-)

Jesús López
MVP

Preguntas similares