Als ich mich vor kurzem mit demselben Problem befassen musste, habe ich mir das so vorgestellt: Zunächst einmal hat Ihre bestehende Klasse eine Aufgabe, nämlich die Bereitstellung einer bestimmten Funktionalität. Es liegt nicht in der Verantwortung des Objekts, thread-sicher zu sein. Wenn es thread-sicher sein muss, sollte ein anderes Objekt verwendet werden, um diese Funktionalität bereitzustellen. Aber wenn ein anderes Objekt die Thread-Safety-Funktionalität bereitstellt, kann es nicht optional sein, weil Sie dann nicht beweisen können, dass Ihr Code Thread-Safe ist. So handhabe ich es also:
// 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
}
}
Die Verwendung von Generika ermöglicht es, in Ihren Tests eine gefälschte Sperre zu verwenden, um zu überprüfen, ob die Sperre immer genommen wird, wenn eine Transaktion erstellt wird, und nicht freigegeben wird, bis die Transaktion beendet ist. Jetzt können Sie auch überprüfen, ob die Sperre beim Aufruf Ihrer wichtigen Methode übernommen wird. Die Verwendung im Produktionscode sollte etwa so aussehen:
// 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();
}
Indem Sie sicherstellen, dass die ImportantImplementation nicht von jemand anderem erstellt werden kann (indem Sie sie z.B. im Provider erstellen und sie zu einer privaten Klasse machen), können Sie nun beweisen, dass Ihre Klasse thread-sicher ist, da auf sie nicht ohne eine Transaktion zugegriffen werden kann und die Transaktion immer die Sperre übernimmt, wenn sie erstellt wird und sie freigibt, wenn sie entsorgt wird.
Sicherzustellen, dass die Transaktion korrekt entsorgt wird, kann schwieriger sein, und wenn dies nicht der Fall ist, kann es zu einem merkwürdigen Verhalten in Ihrer Anwendung kommen. Sie können Tools wie Microsoft Chess (wie in einer anderen Antwort vorgeschlagen) verwenden, um nach solchen Dingen zu suchen. Oder Sie können Ihren Anbieter haben die Fassade zu implementieren und machen es wie folgt implementieren:
public void ImportantMethodThatMustBeThreadSafe()
{
using (ImportantTransaction transaction = CreateTransaction())
{
transaction.Facade.ImportantMethodThatMustBeThreadSafe();
}
}
Auch wenn dies die Implementierung ist, hoffe ich, dass Sie die Tests herausfinden können, um diese Klassen nach Bedarf zu überprüfen.