Pantalla de carga roscada (en espera)
-
11-07-2019 - |
Pregunta
Estoy buscando un método genérico para implementar una pantalla de espera durante operaciones largas. He usado el enhebrado varias veces antes, pero tengo la sensación de que lo implementé muy mal o con demasiadas molestias (y copiar / pegar: ¡el horror!).
Quiero mantener esto lo más genérico y simple posible, por lo que no tendré que implementar un montón de BackgroundWorker
s manejando todo tipo de basura, haciendo que las cosas sean difíciles de mantener.
Esto es lo que me gustaría hacer: tenga en cuenta que esto puede diferir de lo que es realmente posible / mejor práctica / lo que sea, usando VB.NET, Framework 2.0 (por lo que no hay métodos anónimos):
Private Sub HandleBtnClick(sender as Object, e as EventArgs) Handles Button.Click
LoadingScreen.Show()
'Do stuff here, this takes a while!'
Dim Result as Object = DoSomethingTakingALongTime(SomeControl.SelectedObject)
LoadingScreen.Hide()
ProcessResults(Result)
End Sub
La aplicación ahora es completamente de un solo subproceso, por lo que todo se ejecuta en el hilo de la GUI. Necesito poder acceder a objetos en DoSomethingTakingALongTime ()
sin obtener excepciones de hilos cruzados. El hilo de la GUI espera que se complete algún método (que lleva mucho tiempo), mientras que el formulario LoadingScreen
debe responder (está animado / tiene una barra de progreso / etc.).
¿Es este un enfoque factible / bueno o lo veo demasiado simplista? ¿Cuál es la mejor práctica con respecto a este asunto? Y lo más importante: ¿cómo podría implementar tal sistema? Como ya mencioné, tengo muy poca experiencia con el enhebrado, así que sea gentil, por favor :-)
Solución
Su problema es que obtiene una excepción de subproceso cruzado cuando intenta pasar sus datos de subproceso de trabajo a su subproceso de interfaz de usuario. lo que debe hacer es marcar InvokeRequired y comenzar a invocar antes de configurar los controles en su interfaz de usuario para que no reciba el error de esta manera:
Private Sub work_CrossThreadEvent(ByVal sender As Object, ByVal e As System.EventArgs) Handles work.CrossThreadEvent
If Me.InvokeRequired Then
Me.BeginInvoke(New EventHandler(AddressOf work_CrossThreadEvent), New Object() {sender, e})
Return
End If
Me.Text = "Cross Thread"
End Sub
simplemente cambie la parte New EventHandler
al controlador de eventos que está usando.
También creo que usar un trabajador en segundo plano no es un mal método para sus clases de trabajadores, solo cree una clase para su trabajo y use el trabajador de fondo para hacer las cosas de subprocesos de esta manera:
Public MustInherit Class Worker
Protected WithEvents worker As BackgroundWorker
Public Sub New()
worker = New BackgroundWorker()
worker.WorkerReportsProgress = True
worker.WorkerSupportsCancellation = True
End Sub
Public Sub Start()
If (Not worker.IsBusy AndAlso Not worker.CancellationPending) Then
worker.RunWorkerAsync()
End If
End Sub
Public Sub Cancel()
If (worker.IsBusy AndAlso Not worker.CancellationPending) Then
worker.CancelAsync()
End If
End Sub
Protected MustOverride Sub Work()
Private Sub OnDoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles worker.DoWork
Work()
End Sub
Public Event WorkCompelted As RunWorkerCompletedEventHandler
Private Sub OnRunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs) Handles worker.RunWorkerCompleted
OnRunWorkerCompleted(e)
End Sub
Protected Overridable Sub OnRunWorkerCompleted(ByVal e As RunWorkerCompletedEventArgs)
RaiseEvent WorkCompelted(Me, e)
End Sub
Public Event ProgressChanged As ProgressChangedEventHandler
Private Sub OnProgressChanged(ByVal sender As Object, ByVal e As ProgressChangedEventArgs) Handles worker.ProgressChanged
OnProgressChanged(e)
End Sub
Protected Overridable Sub OnProgressChanged(ByVal e As ProgressChangedEventArgs)
RaiseEvent ProgressChanged(Me, e)
End Sub
End Class
Public Class ActualWork
Inherits Worker
Public Event CrossThreadEvent As EventHandler
Protected Overrides Sub Work()
'do work here'
WorkABit()
worker.ReportProgress(25)
WorkABit()
worker.ReportProgress(50)
WorkABit()
worker.ReportProgress(75)
WorkABit()
worker.ReportProgress(100)
End Sub
Private Sub WorkABit()
If worker.CancellationPending Then Return
Thread.Sleep(1000)
RaiseEvent CrossThreadEvent(Me, EventArgs.Empty)
End Sub
End Class
descargo de responsabilidad ... un poco oxidado con vb, pero debería tener la idea.
Otros consejos
En su hilo, use Application.Run (yourform) para obtener lo que quiere.
Tenga en cuenta que debe indicarle al formulario que se cierre de alguna manera.
Espero que no le resulte útil, pero le preguntaría ¿POR QUÉ desea una pantalla de espera con hilos? La razón para usar el subproceso en primer lugar es para que la interfaz de usuario permanezca receptiva y se realicen operaciones largas en segundo plano.
De lo contrario, también podría tener una barra de progreso en su control FormLoading y tener DoSomethingTakingALongTime para actualizarlo periódicamente. Esto no necesitaría hilos en absoluto.