2010-10-06 25 views
9

Supposons que j'ai une classe de collection personnalisée qui fournit une synchronisation de thread interne. Par exemple, une méthode simplifiée Ajouter pourrait ressembler à ceci:Contrats de collection et threads

public void Add(T item) 
    { 
     _lock.EnterWriteLock(); 
     try 
     { 
      _items.Add(item); 
     } 
     finally 
     { 
      _lock.ExitWriteLock(); 
     } 
    } 

Le dernier code des marchés se plaint que CodeContracts: ensures unproven: this.Count >= Contract.OldValue(this.Count). Le problème est que cela ne peut vraiment pas être prouvé. Je peux m'assurer que, en interne, dans la serrure, le nombre sera plus grand que sa valeur précédente. Je ne peux pas assurer cela, cependant, à la sortie de la méthode. Après la fermeture du verrou et avant la fin de la méthode, un autre thread peut émettre deux suppressions (probablement des éléments différents), invalidant le contrat. Le problème fondamental ici est que les contrats de collecte ne peuvent être considérés comme valables que dans un contexte de verrouillage particulier et seulement si le verrouillage est utilisé de manière cohérente dans toute l'application pour tout accès à la collection. Ma collection doit être utilisée à partir de plusieurs threads (avec Add-Remove non conflictuel étant un cas d'utilisation valide), mais j'aimerais quand même implémenter ICollection<T>. Dois-je simplement prétendre que je peux répondre à cette exigence avec un supposé, même si je sais que je ne peux pas? Il me semble qu'aucune des collections de la BCL ne peut assurer cela non plus.


EDIT:

D'après une enquête plus approfondie, il semble que le plus gros problème est que le contrat rewriter peut introduire des affirmations incorrectes, conduisant à la durée d'exécution des échecs. Sur cette base, je pense que ma seule option est de restreindre mon implémentation d'interface à IEnumerable<T>, car le contrat sur ICollection<T> implique que la classe d'implémentation ne peut pas fournir de synchronisation de thread interne (l'accès doit toujours être synchronisé de manière externe). Ceci est acceptable pour mon cas particulier (tous les clients qui souhaitent muter la collection connaissent directement le type de classe), mais je suis certainement intéressé d'entendre s'il y a d'autres solutions à cela.

+0

Si vous utilisez supposer que vous pouvez toujours obtenir des erreurs de contrat d'exécution. –

Répondre

2

Comme vous le sous-entendez, aucun exécutant ne peut respecter ce contrat. En effet, en général face à la multi-threading à moins que le contrat peut être appliqué comme:

Take Lock 
gather Old Stuff 

work 

check Contract, which may compare Old Stuff 
Release Lock 

Je ne vois pas comment pourrait être honoré d'un contrat. De ce que je peux voir here c'est une zone qui n'est pas encore entièrement cuite.

Je pense que l'utilisation d'un Assume est ce que vous pouvez faire de mieux, en effet vous dites "en appelant Add je fais ce que le contrat attend".

+0

Autre idée: si le rédacteur du contrat introduit des assertions pour la condition Assures, cela signifie que les assertions peuvent échouer sous utilisation multi-thread ... –

+1

Cela semble être reconnu dans la référence que je donne. Pour moi, ce contrat ressemble à une bonne idée qui n'est pas prête pour la prime time. – djna

+0

en pensant à ce sujet plus, je pense que cela peut être raisonnable. Les assertions de contrat indiquent essentiellement qu'une classe implémentant l'interface est _not_ thread-safe et, de façon étrange, que vous êtes en train de violer le contrat en essayant de fournir une sécurité de thread interne au niveau de la collection. Je remarque que les nouvelles collections simultanées dans .NET 4 n'implémentent pas ICollection . –

0
using System.Diagnostics.Contracts; 

namespace ConsoleApplication1 
{ 
    class Class1 
    { 
     public int numberOfAdds { get; private set; } 
     public int numberOfRemoves { get; private set; } 
     public int Count 
     { 
      get 
      { 
       return numberOfAdds - numberOfRemoves; 
      } 
     } 

     public void Add() 
     { 
      Contract.Ensures(numberOfAdds == Contract.OldValue(numberOfAdds) + 1); 
     } 

     public void Remove() 
     { 
      Contract.Requires(Count >= 1); 
      Contract.Ensures(numberOfRemoves == Contract.OldValue(numberOfRemoves) + 1); 
     } 

     [ContractInvariantMethod] 
     void inv() 
     { 
      Contract.Invariant(Contract.Result<int>() == numberOfAdds - numberOfRemoves); 
     } 
    } 
} 

Avertissement: n'utilisez pas de râpe moins que des comparaisons; Les comptes déborderont, mais ces contrats devraient fonctionner dans ce cas. Testez avec un petit type entier comme int8. Assurez-vous d'utiliser un type entier qui ne jette pas sur le débordement.