Título: Evitar se congele el formulario al hacer un for
Publicado por: P4nd3m0n1um en 14 Mayo 2016, 05:04 am
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?
Título: Re: Evitar se congele el formulario al hacer un for
Publicado por: kub0x en 14 Mayo 2016, 05:12 am
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!
Título: Re: Evitar se congele el formulario al hacer un for
Publicado por: P4nd3m0n1um en 14 Mayo 2016, 06:27 am
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ó.
Título: Re: Evitar se congele el formulario al hacer un for
Publicado por: kub0x en 14 Mayo 2016, 06:33 am
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
Título: Re: Evitar se congele el formulario al hacer un for
Publicado por: 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: 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
Título: Re: Evitar se congele el formulario al hacer un for
Publicado por: kub0x en 14 Mayo 2016, 07:13 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!
Título: Re: Evitar se congele el formulario al hacer un for
Publicado por: 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
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!
Título: Re: Evitar se congele el formulario al hacer un for
Publicado por: kub0x en 14 Mayo 2016, 10:21 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). 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!
Título: Re: Evitar se congele el formulario al hacer un for
Publicado por: Eleкtro en 14 Mayo 2016, 15:08 pm
https://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.
Título: Re: Evitar se congele el formulario al hacer un for
Publicado por: P4nd3m0n1um en 14 Mayo 2016, 22:00 pm
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
Título: Re: Evitar se congele el formulario al hacer un for
Publicado por: Eleкtro en 14 Mayo 2016, 22:40 pm
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.
|