Cerrar aplicación mientras ser reproduce un bucle Do/Loop

Iniciado por Lekim, 19 Octubre 2015, 01:13 AM

0 Miembros y 1 Visitante están viendo este tema.

Lekim

Hola

Hola me gustaría saber como salir de la aplicación cuando todavía no se a salido de DO/LOOP

Mi método clásico siempre ha sido poner END en el evento Unload en VB y también en Dispose en Net. No se si habrá otra manera también sencilla.

Por ejemplo he creado un Bucle Do/Loop en el que creo un reloj en el cual si han pasado 10 segundos se sale de del bucle. La razón es porque quiero que el bucle dure un mínimo de 10 segundos a espera que se cumpla una condición y si pasados los 10 segundos no se cumple sale del bucle.

Aquí va el supercódigo de ejemplo :P
Código (vbnet) [Seleccionar]
Option Strict On
Option Explicit On
Public Class Form1

   Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
       Dim SecA As TimeSpan, SecB As TimeSpan
       SecA = TimeSpan.FromTicks(Date.Now.Ticks)
       Do
           SecB = TimeSpan.FromTicks(Date.Now.Ticks).Subtract(SecA)
           Me.Text = CStr(SecB.Seconds)
           If CInt(SecB.Seconds) = 10 Then Exit Do
           My.Application.DoEvents()
       Loop 'While (podría poner condición y si se cumple sale antes de los 10 segundos)
   End Sub

   Private Sub Form1_Disposed(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Disposed
       End
   End Sub
End Class


El problema está en que si quito
Código (vbnet) [Seleccionar]
   Private Sub Form1_Disposed(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Disposed
       End
   End Sub


No se puede cerrar la apliación hasta que no pasen los 10 segundos

El Benjo

Lo puedes hacer de varias maneras, la más sencilla es mediante una variable que le diga al loop si la aplicación se ha cerrado, cambiando el estado de esta variable cuando se esté cerrando el form:

Código (vbnet) [Seleccionar]
Public Class Form1

Dim AplicacionCerrada As Boolean = False

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim SecA As TimeSpan, SecB As TimeSpan
SecA = TimeSpan.FromTicks(Date.Now.Ticks)
Do
SecB = TimeSpan.FromTicks(Date.Now.Ticks).Subtract(SecA)
Me.Text = CStr(SecB.Seconds)
If CInt(SecB.Seconds) = 10 Then Exit Do
My.Application.DoEvents()
If AplicacionCerrada = True Then End
Loop 'While (podría poner condición y si se cumple sale antes de los 10 segundos)
End Sub

Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
AplicacionCerrada = True
End Sub

End Class


También puedes hacerlo usando un hilo independiente, pero necesitarías utilizar delegados y usar la propiedad InvokeRequired y la función Invoke() del formulario. Espero que el primer método te sirva.
www.es.neftis-ai.com

Sí hay un mejor lenguaje de programación y es ese con el que puedes desarrollar tus objetivos.

Eleкtro

#2
Terminar la ejecución de la aplicación de forma anómala usando el keyword End es algo que se debe evitar a toda costa, de todas formas en el handler donde indicas esa orden es normal que no te funcione como esperas.

Lo que deberías hacer es lo que ha comentado @El_Benjo al final de su comentario. Puedes utilizar un BackgroundWorker o mejor una Task con un token de cancelación en el cual realizarías una petición de cancelación al cerrar el form.

Aparte de eso, deberías reemplazar el uso de TimeSpan por un StopWatch, ya que la medida es más precisa y su utilización más simple.

Saludos








Lekim

#3
Gracias  los dos ;-)


No estoy seguro que opción es mejor pero creo que por sencillez me decanto por usar la variable boleana.

He aprovechado para realizar un experimento de velocidad de los procesos dentro del bucle, para este ejemplo, que imagino que según el ordenador los resultados será  distinto.

Código (vbnet) [Seleccionar]

Option Explicit On
Option Strict On
Imports System.Text
Public Class Form1
   Dim AplicacionCerrada As Boolean = False
   Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
       Dim stopWatch As New Stopwatch() : Dim ts As TimeSpan : Dim elapsedTime As String = Nothing
       Dim Cont As Integer = 0

       stopWatch.Start()
       Do
           Cont += 1
           ts = stopWatch.Elapsed
           String.Format("{00}", ts.Seconds)

           '///Experimeto que mide el número de procesos por segundo
           Dim Datos As New StringBuilder
           With Datos
               .AppendFormat("Procesos: {0}", CInt(Cont))
               .AppendLine()
               .AppendFormat("Segundos: {0}", ts.Seconds)
               .AppendLine()
               .AppendFormat("Velocidad: {0}", (CInt(Cont) / (CInt(ts.Seconds))).ToString("0.000") & " Proc/s")
           End With
           Label1.Text = Datos.ToString
           '/////////////////////////////////////////////////////////////////////////

           If AplicacionCerrada = True Then End
           If ts.Seconds = 10 Then stopWatch.Stop() : Exit Do
           My.Application.DoEvents()
       Loop While Cont < 14000
   End Sub
   Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As FormClosingEventArgs) Handles MyBase.FormClosing
       AplicacionCerrada = True
   End Sub
End Class





Esta sería la otra forma, aunque más compleja y que me ha costado encontrar.

Código (vbnet) [Seleccionar]

Option Explicit On
Option Strict On
Imports System.Threading.Tasks
Imports System.Text

Public Class Form1

   Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
       Dim stopWatch As New Stopwatch() : Dim ts As TimeSpan : Dim elapsedTime As String = Nothing
       Dim Cont As Integer = 0

       Task.Factory.StartNew(Sub()

                                 stopWatch.Start()
                                 'Desactivar el chequeo sobre acceso de varios subprocesos
                                 ' CheckForIllegalCrossThreadCalls = False

                                 Do
                                     Cont += 1
                                     ts = stopWatch.Elapsed
                                     String.Format("{00}", ts.Seconds)
                                     '///Experimeto que mide el número de procesos por segundo
                                     Dim Datos As New StringBuilder
                                     With Datos
                                         .AppendFormat("Procesos: {0}", CInt(Cont))
                                         .AppendLine()
                                         .AppendFormat("Segundos: {0}", ts.Seconds)
                                         .AppendLine()
                                         .AppendFormat("Velocidad: {0}", (CInt(Cont) / (CInt(ts.Seconds))).ToString("0.000") & " Proc/s")
                                     End With
                                     Label1.Invoke(CType(Sub() Label1.Text = Datos.ToString, MethodInvoker))
                                     '/////////////////////////////////////////////////////////////////////////
                                     If ts.Seconds = 10 Then stopWatch.Stop() : Exit Do
                                     '  My.Application.DoEvents()
                                 Loop While Cont < 14000

                             End Sub).ContinueWith(Sub()
                                                       Me.Invoke(Sub()
                                                                     Me.Text = "Proceso terminado"
                                                                 End Sub)
                                                   End Sub)
   End Sub
End Class



De esta última forma usando Task e Invoke como habéis dicho el programa se cierra sin problemas.

Me ha costado averiguar también como llamar a un control externo dentro de un subproceso, ya que si llamaba diréctamente obtenía error. La forma era la siguiente:

Código (vbnet) [Seleccionar]
Label1.Invoke(DirectCast(Sub() Label1.Text = Datos, MethodInvoker))

También:
Código (vbnet) [Seleccionar]
Me.Invoke(Sub()
       Me.Text = "Proceso terminado"
       End Sub)



En cuanto a la eficiencia creo que es lo mismo porque me dan resultados idénticos, me refiero al experimento.

Si el ordenador es al menos normalito, debería salir del bucle antes de los 10 segundos.

Saludos

Eleкtro

#4
En caso de que hayas optado por utilizar la alternativa de la Task, ten en cuenta como dije que la forma en la que lo debes hacer es asignándole un token de cancelación, cuyo nombre sirve para cancelar la tarea evidentemente, entonces lo usas y seguidamente utilizas el método Task.Wait para esperar a que la tarea finalice su cancelación antes de cerrar/liberar el form, de lo contrario se lanzará una excepción de tipo ObjectDisposedException al intentar invokar el control cuando ya ha sido liberado.

No es que sea muy importante ya que la excepción ocurrirá en el thread secundario, es decir, no saltará un mensaje de error ni nada en el thread de la UI, la excepción la podrás ver al depurar la app pero el form se cerrará como estaba previsto sin mostrar nada, de todas formas lo comento por que siempre es buena costumbre intentar evitar o controlar cualquier excepción que podamos preveer con antelación, en la medida de lo posible.

Saludos!








Lekim

Ok Elektro  :)

Encontré lo que comentas aquí. Lo probaré cuando pueda.

CANCELACIÓN DE SUBPROCESOS ADMINISTRADOS

Saludos