Premièrement - la plupart des classes n'auront jamais besoin d'être thread-safe. Utilisez YAGNI : n'appliquez la sécurité des threads que lorsque vous savez que vous allez réellement l'utiliser (et le tester).
Pour les choses au niveau de la méthode, il y a [MethodImpl]:
[MethodImpl(MethodImplOptions.Synchronized)]
public void SomeMethod() {/* code */}
Cela peut également être utilisé sur les accesseurs (propriétés et événements):
private int i;
public int SomeProperty
{
    [MethodImpl(MethodImplOptions.Synchronized)]
    get { return i; }
    [MethodImpl(MethodImplOptions.Synchronized)]
    set { i = value; }
}
Notez que les événements de type champ sont synchronisés par défaut, tandis que les propriétés implémentées automatiquement ne le sont pas :
public int SomeProperty {get;set;} // not synchronized
public event EventHandler SomeEvent; // synchronized
Personnellement, je n'aime pas l'implémentation MethodImplcar elle se verrouille thisou typeof(Foo)- ce qui est contraire aux meilleures pratiques. L'option préférée consiste à utiliser vos propres verrous:
private readonly object syncLock = new object();
public void SomeMethod() {
    lock(syncLock) { /* code */ }
}
Notez que pour les événements de type champ, l'implémentation de verrouillage dépend du compilateur; dans les anciens compilateurs Microsoft, il s'agit d'un lock(this)/ lock(Type)- cependant, dans les compilateurs plus récents, il utilise desInterlocked mises à jour - donc thread-safe sans les parties désagréables.
Cela permet une utilisation plus granulaire et permet l'utilisation de Monitor.Wait/ Monitor.Pulseetc pour communiquer entre les threads.
Une entrée de blog connexe ( revisitée plus tard ).