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
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
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
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:
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.
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
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.
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.
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:
Label1.Invoke(DirectCast(Sub() Label1.Text = Datos, MethodInvoker))
También:
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
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!
Ok Elektro :)
Encontré lo que comentas aquí. Lo probaré cuando pueda.
CANCELACIÓN DE SUBPROCESOS ADMINISTRADOS (https://msdn.microsoft.com/es-es/library/dd997364(v=vs.110).aspx/)
Saludos