Quand j'ai récemment eu à traiter le même problème, j'ai pensé à lui de cette façon; tout d'Abord votre classe existante a une responsabilité, c'est de donner à certaines fonctionnalités. Ce n'est pas les objets responsabilité d'être thread-safe. Si elle doit être thread-safe autres objets doivent être utilisés pour fournir cette fonctionnalité. Mais si un autre objet est de fournir à l'thread-safe-ness il ne peut pas être une option, car alors vous ne pouvez pas prouver votre code est thread-safe. Donc, c'est la façon dont je le manipuler:
// This interface is optional, but is probably a good idea.
public interface ImportantFacade
{
void ImportantMethodThatMustBeThreadSafe();
}
// This class provides the thread safe-ness (see usage below).
public class ImportantTransaction : IDisposable
{
public ImportantFacade Facade { get; private set; }
private readonly Lock _lock;
public ImportantTransaction(ImportantFacade facade, Lock aLock)
{
Facade = facade;
_lock = aLock;
_lock.Lock();
}
public void Dispose()
{
_lock.Unlock();
}
}
// I create a lock interface to be able to fake locks in my tests.
public interface Lock
{
void Lock();
void Unlock();
}
// This is the implementation I want in my production code for Lock.
public class LockWithMutex : Lock
{
private Mutex _mutex;
public LockWithMutex()
{
_mutex = new Mutex(false);
}
public void Lock()
{
_mutex.WaitOne();
}
public void Unlock()
{
_mutex.ReleaseMutex();
}
}
// This is the transaction provider. This one should replace all your
// instances of ImportantImplementation in your code today.
public class ImportantProvider<T> where T:Lock,new()
{
private ImportantFacade _facade;
private Lock _lock;
public ImportantProvider(ImportantFacade facade)
{
_facade = facade;
_lock = new T();
}
public ImportantTransaction CreateTransaction()
{
return new ImportantTransaction(_facade, _lock);
}
}
// This is your old class.
internal class ImportantImplementation : ImportantFacade
{
public void ImportantMethodThatMustBeThreadSafe()
{
// Do things
}
}
L'utilisation de médicaments génériques rend possible l'utilisation d'un faux de verrouillage dans vos tests pour vérifier que le verrou est toujours à prendre lorsqu'une transaction est créée et pas libéré jusqu'à ce que la transaction est éliminé. Maintenant, vous pouvez également vérifier que le verrou est pris lorsque votre méthode est appelée. L'utilisation dans la production de code devrait ressembler à quelque chose comme ceci:
// Make sure this is the only way to create ImportantImplementation.
// Consider making ImportantImplementation an internal class of the provider.
ImportantProvider<LockWithMutex> provider =
new ImportantProvider<LockWithMutex>(new ImportantImplementation());
// Create a transaction that will be disposed when no longer used.
using (ImportantTransaction transaction = provider.CreateTransaction())
{
// Access your object thread safe.
transaction.Facade.ImportantMethodThatMustBeThreadSafe();
}
En vous assurant que l'ImportantImplementation ne peut pas être créé par quelqu'un d'autre (par exemple la créer dans le fournisseur et d'en faire une classe privée) vous kan maintenant prouver votre classe est thread-safe, puisqu'il ne peut être accédé sans une opération et l'opération prend toujours la serrure lors de leur création et qu'il se dégage lors de son élimination.
Assurez-vous que la transaction est éliminé correctement, peut être plus difficile et si pas, vous risquez de voir des comportements bizarres dans votre application. Vous pouvez utiliser des outils comme Microsoft Échecs (comme suggéré dans un autre anser) à chercher des choses comme ça. Ou vous pouvez avoir votre fournisseur de mettre en œuvre la façade et de la faire appliquer comme ceci:
public void ImportantMethodThatMustBeThreadSafe()
{
using (ImportantTransaction transaction = CreateTransaction())
{
transaction.Facade.ImportantMethodThatMustBeThreadSafe();
}
}
Même si ce n'est la mise en œuvre, je l'espère, vous pouvez trouver des tests pour vérifier ces classes en tant que de besoin.