Tengo un formulario con un listview y un boton, el botón realiza un proceso con cada item de la lista mediante un for, tengo otro listview que es un log, que cada vez que se ejecuta una linea se envía un aviso al log (se agrega un item a listview log), el problema esta en que este for congela al formulario mientras realiza el proceso, una vez que termina, muestra todo el log y se cumplieron todos los procesos pero necesitaría que el mismo no se congele, ya que el usuario tiene que estar pendiente del log para saber si esta funcionando correctamente o no.
Probe con colocar el log en otro formulario y antes de realizar el for abrir dicho formulario para que este quede activo, pero igual se congela tanto el primero como el segundo.
Alguna idea?
Deberías utilizar Threads (hilo) para separar la carga computacional y dejar el main thread (la GUI) sin carga para que se renderice de forma normal, ya que lo que está ocurriendo es que el for invalida o bloquea el proceso de renderizado de la GUI.
https://msdn.microsoft.com/en-us/library/system.threading.thread%28v=vs.110%29.aspx
Saludos!
Probe con el BackgroundWorker pero me da error al querer completar un listview que esta fuera del proceso:
Me.ListView1.Items.Add("Test LOG")
Error:
An exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll but was not handled in user code
Additional information: Operación no válida a través de subprocesos: Se tuvo acceso al control 'ListView1' desde un subproceso distinto a aquel en que lo creó.
No quería decírtelo para no desalentarte en el proceso de gestión de hilos, pero esto es normal. Me explico, si tienes dos hilos cada uno tiene sus recursos locales y obviamente el hilo de la lógica del for no dispone de los recursos del hilo principal (refiriéndome a los controles ej: ListView) por lo tanto tienes que hacer una llamada desde el hilo del for al main thread para gestionar el control.
¿Cómo lo hacemos? Con delegados, que si programaste alguna vez en C/C++ no son más que pointers a function. Es decir, cuando termines el for en el segundo hilo llamas al function pointer que apunta a una func que reside en el primer hilo, de esta forma intercambias la información.
Ésta es la técnica general propuesta por Microsoft para gestionar dicha comunicación: https://msdn.microsoft.com/en-us/library/zyzhdc6b.aspx (con esto se arregla ;) )
Saludos
Haber si entendi, usar el BackgroundWorker y cuando se haga el for en vez de mandarle el codigo, llamar a un función:
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles hiloSegundoPlano.DoWork
Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
Call PasarList2aList1()
End Sub
Function PasarList2aList1(ByVal n As Integer,ByVal worker As BackgroundWorker,ByVal e As DoWorkEventArgs) As Long
For i = 0 To ListView2.Items.Count - 1
Dim oreg As New ListViewItem
Threading.Thread.Sleep("2000")
oreg = Me.ListView1.Items.Add(ListView2.Items(i).Text)
Next
Return
End Function
Cita de: P4nd3m0n1um en 14 Mayo 2016, 07:04 AM
Haber si entendi, usar el BackgroundWorker y cuando se haga el for en vez de mandarle el codigo, llamar a un función
No sé si lo habrás probado, pero no funcionará. Simplemente has separado la lógica del for en otra función (PasarList2aList1).
Lo que tienes que hacer es declarar un delegado que acepte un parámetro de tipo Integer, asignarle la función PasarList2aList1 al objeto delegado e invocarlo desde el BackgroundWorker1_DoWork haciendo Listview1.Invoke(delegado). De esta forma llamas al puntero de la función PasarList2aList1 desde DoWork de manera segura (como pide .NET, c# o vb trabajan igual). Revisa el link que postee antes.
Saludos!
Prueba con DoEvents dentro del ciclo for. Así evitarás la sobrecarga, que es lo que hace que se te pete. :P
https://msdn.microsoft.com/es-es/library/system.windows.forms.application.doevents%28v=vs.110%29.aspx
PD: kub0x, al final terminaste escuchando a moraio chico :laugh: ;-)
saludos!
Cita de: fary en 14 Mayo 2016, 09:24 AM
Prueba con DoEvents dentro del ciclo for. Así evitarás la sobrecarga, que es lo que hace que se te pete. :P
Es una solución parcial y no debería de ser implementada en un ámbito profesional o incluso amateur, dado que se considerá mala práctica en el mundo de .NET. Te dirán que .NET no es vb6.
DoEvents permite procesar los eventos que están en la cola del Event Loop, cosa que tu for está ralentizando, de esta forma los mensajes de renderizado del formulario se procesarán y se dibujará sin problemas y sin hilos (todo en el main thread).
Cita de: fary en 14 Mayo 2016, 09:24 AM
PD: kub0x, al final terminaste escuchando a moraio chico :laugh: ;-)
P.D: Vaya bulerías que se montaba el morao, único en el compás, no he encontrado nada igual, supongo que será intrínseco de Jerez.
Saludos!
Cita de: kub0x en 14 Mayo 2016, 06:33 AMhttps://msdn.microsoft.com/en-us/library/zyzhdc6b.aspx (con esto se arregla ;) )
El compañero @
KuBox ya te ha dado y explicado la solución, particionar la lógica del algoritmo en bloques de método individuales es irse por las ramas, ya que sigues sin evaluar la propiedad que se te mencionó.
Pero aparte del uso de la propiedad
Control.Invoke, es muy recomendable que también utilices la propiedad
Control.InvokeRequired para evitar intentar invocar el control de forma síncrona en caso de que haya sido creado desde el mismo hilo, ya que de lo contrario esto ocasionaría comportamientos indeseados y/o errores, como por ejemplo la recreación del handle del control;
También hay otros controles de errores adicionales que serían bastante imprescindibles como por ejemplo:
· Utilizar la declaración
SyncLock para evitar que múltiples hilos intenten modificar el control, por ejemplo si instancias e inicias el mismo
BackGroundWorker 2 veces al mismo tiempo.
· Comprobar si los recursos del control fueron liberados (
Control.IsDisposed).
· Comprobar si el handle de la ventana se creó, y verificarlo antes de llamar a
Control.InvokeRequired, ya que de lo contrario
Control.InvokeRequired siempre devolverá
False indiferentemente del hilo donde se creó el control.
...Pero por el momento podemos dejarlo con la medida de seguridad básica y principal, que sería, como ya he dicho, evaluar el valor devuelto por
Control.InvokeRequired.
Este sería el código que puedes utilizar:
Imports System.Threading
Public NotInheritable Class Work : Implements IDisposable
#Region " Private Fields "
<EditorBrowsable(EditorBrowsableState.Never)>
Friend WithEvents Bgw As BackgroundWorker
Private ReadOnly mreSync As ManualResetEvent
Private ReadOnly mreAsync As ManualResetEvent
Private isRunSync As Boolean = False
Private isCancelSyncRequested As Boolean = False
Private isPauseRequested As Boolean = False
#End Region
#Region " Properties "
<EditorBrowsable(EditorBrowsableState.Always)>
Public ReadOnly Property IsRunning As Boolean
Get
Return (Me.Bgw IsNot Nothing) AndAlso Not (Me.isPausedB)
End Get
End Property
<EditorBrowsable(EditorBrowsableState.Always)>
Public ReadOnly Property IsPaused As Boolean
Get
Return (Me.Bgw IsNot Nothing) AndAlso (Me.isPausedB)
End Get
End Property
Private isPausedB As Boolean = False
Public ReadOnly Property IsDisposed As Boolean
<DebuggerStepThrough>
Get
Return (Me.Bgw Is Nothing)
End Get
End Property
#End Region
#Region " Constructors "
<DebuggerNonUserCode>
Public Sub New()
Me.Bgw = New BackgroundWorker()
Me.mreSync = New ManualResetEvent(initialState:=False)
Me.mreAsync = New ManualResetEvent(initialState:=True)
End Sub
#End Region
#Region " Public Methods "
<DebuggerStepThrough>
Public Sub Run()
If Not (Me.IsDisposed) AndAlso Not (Me.Bgw.IsBusy) Then
Me.isRunSync = True
With Me.Bgw
.WorkerSupportsCancellation = False
.WorkerReportsProgress = False
.RunWorkerAsync()
End With
Me.mreSync.WaitOne()
Else
Throw New InvalidOperationException("BackGroundWorker is already running.")
End If
End Sub
<DebuggerStepThrough>
Public Sub RunAsync()
If Not (Me.IsDisposed) AndAlso Not (Me.Bgw.IsBusy) Then
With Me.Bgw
.WorkerSupportsCancellation = True
.WorkerReportsProgress = True
.RunWorkerAsync()
End With
Else
Throw New InvalidOperationException("BackGroundWorker is already running.")
End If
End Sub
<DebuggerStepThrough>
Public Sub Pause()
If Not (Me.IsDisposed) AndAlso Not (Me.isPausedB) Then
Me.isPauseRequested = True
Me.isPausedB = True
Me.mreAsync.Reset()
Else
Throw New InvalidOperationException("BackGroundWorker is not running.")
End If
End Sub
<DebuggerStepThrough>
Public Sub [Resume]()
If Not (Me.IsDisposed) AndAlso (Me.isPausedB) Then
Me.isPausedB = False
Me.isPauseRequested = False
Me.mreAsync.Set()
Else
Throw New InvalidOperationException("BackGroundWorker is not paused.")
End If
End Sub
<DebuggerStepThrough>
Public Sub Cancel(Optional ByVal throwOnError As Boolean = False)
Me.isCancelSyncRequested = True
Me.CancelAsync()
Me.mreSync.WaitOne()
Me.isCancelSyncRequested = False
End Sub
<DebuggerStepThrough>
Public Sub CancelAsync(Optional ByVal throwOnError As Boolean = False)
If Not (Me.IsDisposed) AndAlso Not (Me.Bgw.CancellationPending) AndAlso (Me.Bgw.IsBusy) Then
Me.mreAsync.Set() ' Resume thread if it is paused.
Me.Bgw.CancelAsync()
Else
Throw New InvalidOperationException("BackGroundWorker is not initialized.")
End If
End Sub
#End Region
#Region " Private Methods "
<DebuggerStepThrough>
Private Function AddLvItem(ByVal src As ListView, ByVal dst As ListView, ByVal index As Integer) As ListViewItem
Dim result As ListViewItem = Nothing
If (dst.InvokeRequired) Then
dst.Invoke(
Sub()
Try
result = dst.Items.Add(src.Items(index).Text)
Catch ex As Exception
Debug.WriteLine(String.Format("Woker exception occured: {0}", ex.Message))
End Try
End Sub)
Else
Try
result = dst.Items.Add(src.Items(index).Text)
Catch ex As Exception
Debug.WriteLine(String.Format("Woker exception occured: {0}", ex.Message))
End Try
End If
Return result
End Function
#End Region
#Region " Event-Handlers "
<DebuggerStepperBoundary>
Private Sub MyWorker_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) _
Handles Bgw.DoWork
Dim lock As Object = ""
SyncLock lock
Dim lv1 As ListView = WindowsApplication1.Form1.ListView1
Dim lv2 As ListView = WindowsApplication1.Form1.ListView2
Dim max As Integer = lv2.Items.Count
Dim itemIndex As Integer = -1
For i As Integer = 0 To (max - 1)
If (Me.Bgw.CancellationPending) Then
e.Cancel = True
Exit For
Else
If Me.isPauseRequested Then ' Pause this thread.
Me.mreAsync.WaitOne(Timeout.Infinite)
End If
Dim lvi As ListViewItem = Me.AddLvItem(src:=lv2, dst:=lv1, index:=Interlocked.Increment(itemIndex))
If (lvi IsNot Nothing) Then
Debug.WriteLine(String.Format("ListViewItem Text: {0}", lvi.Text))
End If
If Me.Bgw.WorkerReportsProgress Then
Me.Bgw.ReportProgress((i + 1) * (100 \ max))
End If
Thread.Sleep(TimeSpan.FromSeconds(1))
End If
Next i
End SyncLock
If (Me.Bgw.WorkerReportsProgress) AndAlso Not (Me.Bgw.CancellationPending) Then
Me.Bgw.ReportProgress(100)
End If
If (Me.isRunSync) OrElse (Me.isCancelSyncRequested) Then
Me.mreSync.Set()
End If
End Sub
<DebuggerStepperBoundary>
Private Sub Bgw_ProgressChanged(ByVal sender As Object, ByVal e As ProgressChangedEventArgs) _
Handles Bgw.ProgressChanged
Debug.WriteLine(String.Format("Work Progress: {0:00.00}%", e.ProgressPercentage))
End Sub
<DebuggerStepperBoundary>
Private Sub Bgw_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs) _
Handles Bgw.RunWorkerCompleted
If (e.Cancelled) Then
Debug.WriteLine("Work state: Cancelled")
ElseIf (e.Error IsNot Nothing) Then
Debug.WriteLine("Work state: Error")
Else
Debug.WriteLine("Work state: Success")
End If
Me.Dispose()
End Sub
#End Region
#Region " IDisposable Implementation "
Private isDisposedB As Boolean
<DebuggerStepThrough>
Public Sub Dispose() Implements IDisposable.Dispose
Me.Dispose(isDisposing:=True)
GC.SuppressFinalize(obj:=Me)
End Sub
<DebuggerStepThrough>
Private Sub Dispose(ByVal isDisposing As Boolean)
If (Not Me.isDisposedB) AndAlso (isDisposing) Then
Me.Bgw.Dispose()
Me.Bgw = Nothing
With Me.mreSync
.SafeWaitHandle.Close()
.SafeWaitHandle.Dispose()
.Close()
.Dispose()
End With
With Me.mreAsync
.SafeWaitHandle.Close()
.SafeWaitHandle.Dispose()
.Close()
.Dispose()
End With
End If
Me.isDisposedB = True
End Sub
#End Region
#Region " Hidden Methods "
<EditorBrowsable(EditorBrowsableState.Never)>
<DebuggerNonUserCode>
Public Shadows Function GetHashCode() As Integer
Return MyBase.GetHashCode
End Function
<EditorBrowsable(EditorBrowsableState.Never)>
<DebuggerNonUserCode>
Public Shadows Function [GetType]() As Type
Return MyBase.GetType
End Function
<EditorBrowsable(EditorBrowsableState.Never)>
<DebuggerNonUserCode>
Public Shadows Function Equals(ByVal obj As Object) As Boolean
Return MyBase.Equals(obj)
End Function
<EditorBrowsable(EditorBrowsableState.Never)>
<DebuggerNonUserCode>
Public Shadows Function ToString() As String
Return MyBase.ToString
End Function
#End Region
End Class
Ejemplo de uso:
Public NotInheritable Class Form1 : Inherits Form
Friend MyWork As Work
Private Sub Button1_Click() Handles Button1.Click
If (Me.MyWork IsNot Nothing) Then
Me.MyWork.Cancel()
End If
Me.MyWork = New Work
Me.MyWork.RunAsync()
End Sub
End Class
Como se puede ver, la clase donde encapsulo el BackGroundWorker tiene varias funcionalidades añadidas para controlar operaciones sincrónicas y asincrónicas, si quieres ver la implementación completa y original, y con la documentación XML, aquí lo tienes:
- Elektro's Custom BGW Implementation (http://pastebin.com/bPpWi0mK)
Saludos.
probe pero me da:
An exception of type 'System.InvalidOperationException' occurred in AutoLog.exe but was not handled in user code
Additional information: Error al crear el formulario. Consulte Exception.InnerException para obtener más detalles. Error: No se puede crear una instancia del control ActiveX '8856f961-340a-11d0-a96b-00c04fd705a2' porque el subproceso actual no está en un contenedor uniproceso.
En delphi no pasaba esto.. :-( jajaja
Por favor, cuando especifiques un error, muestra el código que lo genera. No somos adivinos.
De todas formas el mensaje de la excepción creo que te está indicando que por algún motivo en "X" miembro debes habilitar la comunicación entre los threads que se quieran comunicar con ese miembro via COM, y para ello ese miembro debe ejecutarse en un entorno de único hilo (single threaded apartment a.k.a.
STA). Dicho miembro puede ser el punto de entrada de tu aplicación, u otro método, según lo que hagas en tu código y como lo estés haciendo.
Debes añadirle este atributo a dicho método:
- STAThreadAttribute | MSDN (https://msdn.microsoft.com/en-us/library/system.stathreadattribute%28v=vs.110%29.aspx)
Si estás usando un objeto de tipo
Thread, también prueba utilizando el método
Thread.SetApartmentState antes de iniciarlo.
- Thread.SetApartmentState() | MSDN (https://msdn.microsoft.com/en-us/library/system.threading.thread.setapartmentstate%28v=vs.110%29.aspx)
Saludos.