2010-03-04 7 views
3

Je rencontre un problème que j'ai pu résoudre avec Application.DoEvents, mais je ne veux pas le laisser car il pourrait introduire toutes sortes de problèmes désagréables.Vous voulez appeler même BackgroundWorker plusieurs fois sans utiliser Application.DoEvents

Contexte: Notre application est principalement une application de bureau qui effectue de nombreux appels à un service Web. Nous contrôlons tout mais les modifications apportées à la conception globale du système ne seront pas sérieusement prises en compte. L'un de ces appels, Calculer, est très souvent utilisé, et il peut parfois prendre quelques minutes pour traiter toutes les données afin de renvoyer des résultats valides. Auparavant, cet appel à Calculate était effectué de manière synchrone et bloquait ainsi l'interface utilisateur, laissant l'utilisateur se demander si l'application était gelée ou non, etc. J'ai réussi à transférer tous les longs appels à BackgroundWorker, puis j'ai effectué un écran d'attente simple qui ferait défiler un message animé "Calculer ...".

Maintenant, le problème se pose lorsque notre code d'interface utilisateur tente d'appeler à nouveau le sous-programme de calcul avant la fin du premier. Je recevrais un message "This BackgroundWorker est actuellement occupé et ne peut pas exécuter plusieurs instances ...". Ce que je pensais devrait être contrôlé par les appels resetEvent.WaitOne(). Ce n'était pas le cas, je pensais que peut-être un autre événement contrôlant l'accès à la routine entière aiderait, donc j'ai ajouté le calcDoneEvent. Cela ne résolvait toujours pas le problème, mais le bloquait indéfiniment lors du 2ème appel de l'appel calcDoneEvent.WaitOne() de Calculate. Ensuite, sur un coup de tête, j'ai ajouté le Application.DoEvents au fond de Calculer et alto, problème résolu.

Je ne veux pas laisser cela. Eviter là parce que je l'ai lu peut causer des problèmes qui plus tard sont très difficiles à traquer. Y a-t-il une meilleure façon de gérer cette situation?

Merci à l'avance ..

Private WithEvents CalculateBGW As New System.ComponentModel.BackgroundWorker 
Dim resetEvent As New Threading.AutoResetEvent(False) 
Dim calcDoneEvent As New Threading.AutoResetEvent(True) 

Public Sub Calculate() 

    calcDoneEvent.WaitOne() ' will wait if there is already a calculate running.' 
    calcDoneEvent.Reset() 

    ' setup variables for the background worker' 

    CalculateBGW.RunWorkerAsync() ' Start the call to calculate' 

    Dim nMsgState As Integer = 0 
    ' will block until the backgorundWorker is done' 
    Do While Not resetEvent.WaitOne(200) ' sleep for 200 miliseconds, then update the status window' 
     Select Case nMsgState 
      Case 1 
       PleaseWait(True, vbNull, "Calculating. ") 
      Case 2 
       PleaseWait(True, vbNull, "Calculating.. ") 
      Case 3 
       PleaseWait(True, vbNull, "Calculating... ") 
      Case 4 
       PleaseWait(True, vbNull, "Calculating....") 
      Case Else 
       PleaseWait(True, vbNull, "Calculating ") 
     End Select 
     nMsgState = (nMsgState + 1) Mod 5 
    Loop 

    PleaseWait(False, vbNull) 'make sure the wait screen goes away' 

    calcDoneEvent.Set() ' allow another calculate to proceed' 
    Application.DoEvents() ' I hate using this here' 
End Sub 

Private Sub CalculateBGW_DoWork(ByVal sender As System.Object, _ 
    ByVal e As System.ComponentModel.DoWorkEventArgs) Handles CalculateBGW.DoWork 
    Try 
     'make WS Call, do data processing on it, can take a long time..' 
     'No Catch inside the DoWork for BGW, or exception handling wont work right...' 
     'Catch' 
    Finally 
     resetEvent.Set() 'unblock the main thread' 
    End Try 
End Sub 

Private Sub CalculateBGW_RunWorkerCompleted(ByVal sender As Object, _ 
    ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles CalculateBGW.RunWorkerCompleted 

    'If an error occurs we must check e.Error prior to touching e.Result, or the BGW' 
    'will possibly "eat" the exception for breakfast (I hear theyre tasty w/ jam)' 
    If Not (e.Error Is Nothing) Then 

     'If a Web Exception timeout, retry the call' 
     If TypeOf e.Error Is System.Net.WebException And _ 
      e.Error.Message = "The operation has timed out" And _ 
      intRetryCount < intRetryMax Then 

      ' Code for checking retry times, increasing timeout, then possibly recalling the BGW' 

      resetEvent.Reset() 
      CalculateBGW.RunWorkerAsync() 'restart the call to the WS' 
     Else 
      Throw e.Error ' after intRetryMax times, go ahead and throw the error up higher' 
     End If 
    Else 
     Try 

      'normal completion stuff' 

     Catch ex As Exception 
      Throw 
     End Try 
    End If 

End Sub 

Répondre

4

Vous avez déclaré:

Private WithEvents CalculateBGW As New System.ComponentModel.BackgroundWorker 
Dim resetEvent As New Threading.AutoResetEvent(False) 
Dim calcDoneEvent As New Threading.AutoResetEvent(True) 

comme privés champs de la classe contenant. Notez que de cette façon, tous les appels à RunWorkerAsync() sont renvoyés à l'instance même objet de la classe BackgroundWorker (c'est-à-dire au même objet). C'est pourquoi il est "occupé". Ce code est construit pour contenir un seul BackgroundWorker à un moment donné. Si vous voulez autoriser le code UI à appeler la méthode Calculate() chaque fois que nécessaire, vous devez déclarer CalculateBGW en tant que variable locale dans la méthode Calculate(), créant ainsi une nouvelle instance de la classe BackgroundWorker à chaque appel (et ils fonctionneront asynchronosly). Cela signifie que vous devrez ajouter et supprimer les gestionnaires d'événements à l'intérieur de Calculate(), en utilisant AddHandler et RemoveHandler.

Il existe plusieurs approches pour mettre à jour l'interface utilisateur sur la progression, mais il est suggéré d'utiliser l'événement BackgroundWorker.ProgressChanged et la méthode BackgroundWorker.ReportProgress.

Utilisez l'événement BackgroundWorker.RunWorkerCompleted comme déclencheur de rappel, en signalant l'interface utilisateur que le calcul est terminé, déclenchant ainsi le code nécessaire pour représenter le résultat. Cette approche élimine le besoin de maintenir un thread en boucle autour de l'enfilage du thread de calcul - éliminant ainsi le besoin de DoEvents(). Il laisse le thread de calcul informer son patron quand il a fini de travailler, au lieu d'avoir le boss vérifiant le statut du travailleur et d'aller dormir encore et encore.

+0

Je pense que vous pourriez être sur quelque chose là-bas. Si cela ne prenait pas tellement de temps pour rouvrir l'IDE sur cet ancien ordinateur, je l'essayerais maintenant, mais au lieu de cela, il attendra jusqu'au matin. –