2009-02-09 8 views
24

Encore et encore Je dois écrire des versions thread-safe de BindingList et ObservableCollection car, lorsqu'elles sont liées à l'interface utilisateur, ces commandes ne peuvent pas être modifiées à partir de plusieurs threads. Qu'est-ce que j'essaie de comprendre est pourquoi c'est le cas - est-ce un défaut de conception ou ce comportement est-il intentionnel?Pourquoi les classes comme BindingList ou ObservableCollection ne sont-elles pas sûres pour les threads?

Répondre

31

Le problème est de concevoir une collection de thread thread n'est pas simple. Bien sûr, c'est assez simple pour concevoir une collection qui peut être modifiée/lue à partir de plusieurs threads sans corrompre l'état. Mais il est beaucoup plus difficile de concevoir une collection utilisable étant donné qu'elle est mise à jour à partir de plusieurs threads. Prenez le code suivant comme exemple. Supposons que myCollection est une collection thread-safe où les ajouts et mises à jour sont garantis de ne pas corrompre l'état. Ce code n'est pas thread-safe et est une condition de concurrence.

Pourquoi? Même si myCollection est sûr, il n'y a aucune garantie qu'une modification ne se produise pas entre les deux appels de méthode à myCollection: namedly Count et l'indexeur. Un autre thread peut entrer et supprimer tous les éléments entre ces appels.

Ce type de problème rend l'utilisation d'une collection de ce type franchement un cauchemar. Vous ne pouvez jamais laisser la valeur de retour d'un appel influencer un appel ultérieur sur la collection.

EDIT

J'élargi cette discussion sur un récent blog: http://blogs.msdn.com/jaredpar/archive/2009/02/11/why-are-thread-safe-collections-so-hard.aspx

+0

J'ai exactement le même problème. Comment utiliser Dispatcher ou ajouter un élément à ma BindingList à partir d'un thread d'arrière-plan? – Houman

+0

Oui, En bref, la conception des interfaces * .NET collection * n'est pas appropriée pour la sécurité des threads. Comme le signale Jared par exemple, la propriété Count est inutile dans un environnement multithread. –

+0

Il existe de nombreux sous-ensembles utiles de fonctionnalité «IList » qui peuvent être implémentés de manière thread-safe. Les éléments qui suppriment des éléments ou déplacent des objets seraient problématiques, mais le reste de l'interface constituerait un sous-ensemble utile pour de nombreuses applications. Une version de 'Add' qui a rapporté l'index de l'élément ajouté serait utile, mais toutes les applications n'en auraient pas besoin. Si chaque élément ajouté continue à exister dans le même emplacement pour la durée de vie de la liste, une implémentation IList thread-safe peut être utile dans de nombreux scénarios multithread sans verrouillage externe. – supercat

6

Pour ajouter un peu à une excellente réponse de Jared: la sécurité des fils ne vient pas gratuitement. Beaucoup (la plupart?) Des collections ne sont utilisées que dans un seul thread. Pourquoi ces collections devraient-elles avoir des pénalités de performance ou de fonctionnalité pour faire face à l'affaire multithread?

+0

Eh bien, peut-être que je devrais le reformuler ensuite: pourquoi le framework ne fournit-il pas un ThreadSafeObservableCollection ou un tel? –

+0

C'est une question plus raisonnable - mais la réponse de Jared intervient. Cela dépend vraiment de ce que vous entendez par "thread-safe" - ce qui n'est pas un simple drapeau oui/non. –

2

Si vous voulez devenir fou - here's un ThreadedBindingList<T> qui fait des notifications sur le fil de l'interface utilisateur automatiquement. Cependant, il ne serait toujours pas prudent pour un thread d'effectuer des mises à jour, etc. à la fois.

+0

Cette implémentation ne fait que marshaler 'adds' au thread de contexte de synchronisation: ne protège pas contre un certain nombre d'autres conditions de concurrence et/ou de collecte d'erreurs modifiées lors de l'énumération de la liste. – piers7

5

idées Rassembler de toutes les autres réponses, je pense que c'est la façon la plus simple pour résoudre vos problèmes:

Changez votre question: « Pourquoi est-ce pas classe X sain d'esprit »

à

"Quelle est la façon saine de le faire avec classe X?"

  1. dans le constructeur de votre classe, obtenir la displatcher actuelle que vous créez vos collections observables. Becuase, comme vous l'avez souligné, la modification doit être fait sur le thread original, qui peut ne pas être le principal thread graphique. So App.Current.Dispatcher n'est pas toujours droit, et pas toutes les classes ont un this.Dispatcher.

    _dispatcher = System.Windows.Threading.Dispatcher.CurrentDispatcher; 
    _data = new ObservableCollection<MyDataItemClass>(); 
    
  2. Utilisez le répartiteur pour Invoquer vos sections de code qui ont besoin le fil d'origine.

    _dispatcher.Invoke(new Action(() => { _data.Add(dataItem); })); 
    

Cela devrait faire l'affaire pour vous. Bien qu'il existe des situations que vous pourriez préférer .BeginInvoke au lieu de .Invoke.