2009-03-17 8 views
3

Si j'ai besoin de générer un ensemble de données assez volumineux à l'aide de LINQ, cela peut prendre un certain temps (disons quelques secondes) et je dois) générer un retour à l'utilisation de% 'âge fait, y a-t-il un moyen facile/préféré de le faire?Comment signaler une progression lors de l'exécution d'une expression LINQ sur un ensemble de données volumineux

exemple, dire que j'ai la liste A avec 1000 voitures et la liste B avec 1000 camions et je veux sélectionner toutes les paires possibles commandé (voiture, camion) où car.color == lien truck.color ceci:

var pairs = from car in A 
      from truck in B 
      where car.color==truck.color 
      select new {car, truck}; 

Maintenant, à un moment donné, cela sera évalué comme un ensemble de boucles foreach imbriquées. Je voudrais être en mesure de signaler% 'âge complet pendant qu'il interagit et idéalement mettre à jour une barre de progression ou quelque chose.

EDIT: Juste après ma requête, je stocke le résultat dans une variable membre comme une telle liste (qui force la requête à exécuter):

mPairs = pairs.ToList(); 

Je le fais parce que je suis exécutais cela dans un thread de travail d'arrière-plan car je ne veux pas que le thread de l'interface utilisateur se fige car il évalue l'expression LINQ à la demande sur le thread UI (c'est dans Silverlight BTW). C'est pourquoi je voudrais signaler les progrès. L'UX est essentiellement ceci:

  1. Un utilisateur fait glisser un élément sur l'espace de travail
  2. Le moteur démarre alors sur un fil d'arrière-plan pour déterminer les (nombreuses) possibilités de connexion à tous les autres éléments de l'espace de travail.
  3. Pendant que le moteur calcule l'interface utilisateur n'autorise pas les nouvelles connexions ET les rapports indiquent si le nouvel élément sera "connectable" aux autres éléments (tous les chemins de connexion possibles non utilisés ont été déterminés via LINQ).
  4. Lorsque le moteur termine le calcul (requête), l'élément est connectable dans l'interface utilisateur et les chemins de connexion possibles sont stockés dans une variable locale pour une utilisation ultérieure (par exemple, lorsque l'utilisateur clique pour connecter l'élément en fonction de ce mis en évidence a été calculé quand il a été ajouté)

(un processus similaire doit se produire sur la suppression d'un article)

Répondre

2

Quelque chose que j'ai utilisé qui a bien fonctionné était un adaptateur pour le DataContext qui renvoyait un compte du nombre d'éléments qu'il a cédé.

public class ProgressArgs : EventArgs 
{ 
    public ProgressArgs(int count) 
    { 
     this.Count = count; 
    } 

    public int Count { get; private set; } 
} 

public class ProgressContext<T> : IEnumerable<T> 
{ 
    private IEnumerable<T> source; 

    public ProgressContext(IEnumerable<T> source) 
    { 
     this.source = source; 
    } 

    public event EventHandler<ProgressArgs> UpdateProgress; 

    protected virtual void OnUpdateProgress(int count) 
    { 
     EventHandler<ProgressArgs> handler = this.UpdateProgress; 
     if (handler != null) 
      handler(this, new ProgressArgs(count)); 
    } 

    public IEnumerator<T> GetEnumerator() 
    { 
     int count = 0; 
     foreach (var item in source) 
     { 
      // The yield holds execution until the next iteration, 
      // so trigger the update event first. 
      OnUpdateProgress(++count); 
      yield return item; 
     } 
    } 

    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return GetEnumerator(); 
    } 
} 

Utilisation

var context = new ProgressContext(
    from car in A 
    from truck in B 
    select new {car, truck}; 
); 
context.UpdateProgress += (sender, e) => 
{ 
    // Do your update here 
}; 

var query = from item in context 
      where item.car.color==item.truck.color; 

// This will trigger the updates 
query.ToArray(); 

Le seul problème est que vous ne pouvez pas faire facilement un pourcentage à moins que vous connaissez le nombre total. Pour effectuer un comptage total, il faut souvent traiter toute la liste, ce qui peut être coûteux. Si vous connaissez au préalable le nombre total, vous pouvez calculer un pourcentage dans le gestionnaire d'événements UpdateProgress.

+0

J'aime vraiment cette approche. Mon expression LINQ se compose de plusieurs imbriqués - je pourrais mettre ceci autour de la liste la plus externe pour au moins obtenir un indicateur de progression brut (qui est probablement tout ce dont j'ai besoin de toute façon car je ne veux pas trop payer de mise à jour). – caryden

4

EDIT: Cela ne fonctionne pas actuellement parce que les expressions de requête ne permettent pas des accolades. Édition ...

vous pouvez toujours ajouter un "no-op" select ou where clause qui a enregistré des progrès:

public class ProgressCounter 
{ 
    private readonly int total; 
    private int count; 
    private int lastPercentage; 

    public ProgressCounter(int total) 
    { 
     this.total = total; 
    } 

    public void Update() 
    { 
     count++; 
     int currentPercentage = (count * 100)/total; 
     if (currentPercentage != lastPercentage) 
     { 
      Console.WriteLine("Done {0}%", currentPercentage); 
      lastPercentage = currentPercentage; 
     } 
     return true; 
    } 
} 

... 

var progressCounter = new ProgressCounter(A.Count * B.Count); 

var pairs = from car in A 
      from truck in B 
      where progressCounter.Update() 
      where car.color==truck.color 
      select new {car, truck}; 

Notez l'utilisation d'effets secondaires, ce qui est toujours méchant. J'espère que vous utiliseriez une jointure si c'était vraiment la requête, btw :)

Nous avons pensé à ajouter une sorte d'opérateur comme ceci à MoreLINQ - appelé Pipe, Apply, Via, ou quelque chose comme ça.

1

La plus grande partie de Linq est effectuée en utilisant une évaluation paresseuse. La requête n'est donc pas exécutée tant que vous n'avez pas forgé le résultat. Chaque fois que vous «tirez» un résultat de paires, une partie de la requête est évaluée. Cela signifie que vous pouvez simplement afficher des progres dans la boucle foreach qui parcourent le résultat. L'inconvénient est que vous ne savez pas à l'avance quelle sera la taille du jeu de résultats, en comptant la taille de l'ensemble de résultats va également parcourir les résultats et exécuter la requête.

0

Eh bien, le mien était similaire à celui de Jon, même si je suis sûr que son approche serait beaucoup plus concise. Vous pouvez pirater quelque chose ensemble dans le même moyen ..

var pairs = from car in A 
      from truck in B 
      let myProgress = UpdateProgress(...) 
      where car.color == truck.color 
      select new { car, truck }; 

private int UpdateProgress(...) 
{ 
    Console.WriteLine("Updating Progress..."); 
    return -1; 
} 

Bien que mentionné la requête ne sera pas exécutée jusqu'à ce qu'il soit itéré. Cela a également l'inconvénient supplémentaire de créer une nouvelle variable de portée dans la requête.

Questions connexes