2010-12-10 71 views
2

Avec le code ci-dessous, les mises à jour finales de l'interface utilisateur effectuées dans la version finale de l'opération Continueront jamais eu lieu. Je pense que c'est à cause de l'attente() que j'ai à la fin.La séquence du flux de travail de tâche est erronée

La raison pour laquelle je fais cela est parce que sans l'attente, la méthode retournera le IDataProvider avant que son fini soit construit en arrière-plan.

Quelqu'un peut-il m'aider à faire les choses correctement?

Cheers,
Berryl

 private IDataProvider _buildSQLiteProvider()   
    { 

     IDataProvider resultingDataProvider = null; 
     ISession session = null; 

     var watch = Stopwatch.StartNew(); 

     var uiContext = TaskScheduler.FromCurrentSynchronizationContext(); 

     // get the data 
     var buildProvider = Task.Factory 
      .StartNew(
       () => 
        { 
         // code to build it 
        }); 

     // show some progress if we haven't finished 
     buildProvider.ContinueWith(
      taskResult => 
      { 
       // show we are making progress; 
      }, 
      CancellationToken.None, TaskContinuationOptions.None, uiContext); 

     // we have data: reflect completed status in ui 
     buildProvider.ContinueWith(
      dataProvider => 
      { 
       // show we are finished; 
      }, 
      CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, uiContext); 

     try { 
      buildProvider.Wait(); 
     } 

     catch (AggregateException ae) 
     { 
      foreach (var e in ae.InnerExceptions) 
       Console.WriteLine(e.Message); 
     } 
     Console.WriteLine("Exception handled. Let's move on."); 

     CurrentSessionContext.Bind(session); 

     return resultingDataProvider; 
    } 

====

juste pour être clair

Je ne parviens pas à parler au fil ui. Le premier continue avec les mises à jour l'interface utilisateur juste très bien. Le problème que j'ai est le calendrier de la dernière mise à jour de l'interface utilisateur et le retour du fournisseur de données. J'ai commenté une partie du code pour réduire le niveau de bruit dans ce message et me concentrer sur le séquençage des tâches.

====

ok, le code de travail

 private void _showSQLiteProjecPicker()   
    { 

     var watch = Stopwatch.StartNew(); 
     var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext(); 
     ISession session = null; 

     // get the data 
     var buildProvider = Task.Factory.StartNew(
       () => 
       { 
        var setProgress = Task.Factory.StartNew(
         () => 
         { 
          IsBusy = true; 
          Status = string.Format("Fetching data..."); 
         }, 
         CancellationToken.None, TaskCreationOptions.None, uiScheduler); 

        var provider = new SQLiteDataProvider(); 
        session = SQLiteDataProvider.Session; 
        return provider; 
       }); 


     buildProvider.ContinueWith(
      buildTask => 
       { 
        if(buildTask.Exception != null) { 
         Console.WriteLine(buildTask.Exception); 
        } 
        else { 
         Check.RequireNotNull(buildTask.Result); 
         Check.RequireNotNull(session); 

         _updateUiTaskIsComplete(watch); 

         CurrentSessionContext.Bind(session); 
         var provider = buildTask.Result; 
         var dao = provider.GetActivitySubjectDao(); 
         var vm = new ProjectPickerViewModel(dao); 
         _showPicker(vm); 
        } 
       }, 
      CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, uiScheduler); 
    } 

Répondre

3

MISE À JOUR DESSOUS
Ce code ne ressemble pas à elle justifie TPL pour moi. On dirait que c'est peut-être un bon usage pour un BackgroundWorker à la place!

De toute façon, les mises à jour ne sont probablement pas en cours car vous ne pouvez pas mettre à jour l'interface utilisateur à partir d'un thread séparé - vous devez exécuter la mise à jour sur le thread UI. Vous devez utiliser le répartiteur pour cette (http://stackoverflow.com/questions/303116/system-windows-threading-dispatcher-and-winforms contient des informations pour les WPF et WinForms)

Mise à jour:

Donc, j'ai évidemment manqué une partie du code alors voici une réponse révisée. Tout d'abord, Nicholas est correct - .ContinueWith renvoie une nouvelle tâche (http://msdn.microsoft.com/en-us/library/dd270696.aspx). Ainsi, au lieu de

var result = Task.Factory.StartNew(...); 
result.ContinueWith(...); 

vous voulez probablement créer une nouvelle tâche, puis faire tous les appels ContinueWith() et attribuer à la tâche, puis appelez .Start() la tâche. Quelque chose comme:

var task = new Task(...).ContinueWith(...); 
task.Start(); 

Cependant, il y a un défaut dans la conception pour commencer (comme je le vois)! Vous essayez d'exécuter ce code async, c'est pourquoi vous utilisez des threads et TPL. Cependant, vous appelez buildProvider.Wait(); sur le thread d'interface utilisateur qui bloque le thread d'interface utilisateur jusqu'à ce que cette tâche se termine! Mis à part le problème de repeindre l'interface utilisateur dans le ContinueWith() alors que le thread de l'interface utilisateur est bloqué, il n'y a aucun avantage à multithreading ici puisque vous bloquez le thread UI (un non-non majeur). Qu'est-ce que vous voulez probablement faire est de coller le Bind() -ing à l'intérieur d'un ContinueWith ou quelque chose de sorte que vous ne devez pas appeler Wait() et bloquer le fil de l'interface utilisateur.Mon $ 0.02 est que si vous attendez que la requête prenne beaucoup de temps ce que vous voulez vraiment, c'est 2 threads (ou tâches dans TPL) - un pour effectuer la requête et un pour mettre à jour l'interface utilisateur avec des intervalles avec statut. Si vous ne vous attendez pas à ce que cela dure si longtemps, je pense que vous voulez juste un seul thread (tâche) à interroger et ensuite mettre à jour l'interface utilisateur quand c'est fait. Je ferais probablement cela via BackgroundWorker. TPL a été conçu pour gérer beaucoup de tâches et de continuations, mais cela semble trop compliqué pour ce genre de choses - je pense que vous pourriez le faire en utilisant un BackgroundWorker avec beaucoup moins de code. Mais vous mentionnez que vous voulez utiliser TPL ce qui est bien, mais vous allez devoir retravailler un peu pour que ça fonctionne en arrière-plan!

PS - vous avez probablement destiné à mettre le Console.WriteLine("Exception handled. Let's move on."); à l'intérieur du catch

+0

BGW apporte son propre ensemble de problèmes mais je suis d'accord que ce serait bien pour ce scénario. Je soupçonne que le TPL sera plus facile et plus flexible une fois que je l'aurai compris, et cette question concerne l'utilisation de la TPL, pas la manière de rendre compte des progrès en général. Plus important encore, si vous regardez le code, je capture le contexte de l'interface utilisateur sans être dépendant de WPF (une bonne chose), et le premier ContinueWith * fait "mettre à jour le ui. Cheers – Berryl

+0

@Statis. Oui, l'attente à la fin était un perdant, tout comme le premier ContinueWith à mettre à jour la progression.Le dernier ContinueWith était conceptuellement correct, mais au lieu du premier ContinueWith, il doit être une nouvelle tâche (comme you & Nicholas suggéré) MAIS il doit être imbriqué dans la tâche d'origine, ET il doit avoir le thread UI.En plus de vouloir apprendre le TPL et normaliser mon approche de la programmation asynchrone, je vais éventuellement vouloir greffer d'autres tâches ici sera douloureux avec la BGW Merci pour votre aide. – Berryl

2

Je suis un peu brumeux, mais la dernière fois que je l'utilise TPL je l'ai trouvé confus. ContinueWith() renvoie une nouvelle instance . Vous devez donc affecter le deuxième résultat ContinueWith() à une nouvelle variable, par exemple var continuedTask = builderProvider.ContinueWith(...), puis remplacer le dernier par la référence continuedTask.ContinueWith() au lieu de buildProvider.ContinueWith(). Puis Wait() sur le dernier Task.

Espérons que ça aide!