L'exemple de code fourni laisse certainement ouvert un grand nombre de questions sur votre cas d'utilisation particulier ; cependant, je vais tenter de répondre à la stratégie générale de mise en œuvre de ce type de problème pour un environnement multithread.
Le contexte ou ses données sont-ils modifiés de manière couplée et non atmoïque ?
Par exemple, est-ce que l'une de vos commandes ferait quelque chose comme :
Context.Data.Item1 = "Hello"; // Setting both values is required, only
Context.Data.Item2 = "World"; // setting one would result in invalid state
Dans ce cas, il faut absolument que vous utilisiez lock(...)
quelque part dans votre code. La question est de savoir où.
Quel est le comportement de vos contrôleurs imbriqués en matière de sécurité des threads ?
Dans le code d'exemple GIST lié, l'élément CommandContext
La classe a des propriétés ServerController
y ServiceController
. Si vous n'êtes pas le propriétaire de ces classes, vous devez vérifier soigneusement la documentation sur la sécurité des threads de ces classes également.
Par exemple, si vos commandes s'exécutant sur deux threads différents effectuent des appels tels que :
Context.ServiceController.Commit(); // On thread A
Context.ServiceController.Rollback(); // On thread B
Il est fort possible que ces deux actions ne puissent être invoquées simultanément si le créateur de la classe de contrôleur ne s'attendait pas à une utilisation multithread.
Quand verrouiller et sur quoi verrouiller
Prenez le verrou chaque fois que vous devez effectuer plusieurs actions qui doivent se dérouler complètement ou pas du tout, ou lorsque vous invoquez des opérations de longue durée qui ne s'attendent pas à un accès simultané. Libérez le verrou dès que possible.
De même, les verrous ne doivent être pris que sur des propriétés ou des champs en lecture seule ou constants. Donc avant de faire quelque chose comme :
lock(Context.Data)
{
// Manipulate data sub-properties here
}
N'oubliez pas qu'il est possible d'échanger l'objet qui Data
pointe vers. L'implémentation la plus sûre consiste à fournir un objet de verrouillage spécial :
internal readonly object dataSyncRoot = new object();
internal readonly object serviceSyncRoot = new object();
internal readonly object serverSyncRoot = new object();
pour chaque sous-objet qui nécessite un accès et une utilisation exclusifs :
lock(Context.dataSyncRoot)
{
// Manipulate data sub-properties here
}
Il n'y a pas de recette miracle pour savoir quand et où placer les verrous, mais en général, plus vous les placez haut dans la pile d'appels, plus votre code sera probablement simple et sûr, au détriment des performances - puisque les deux threads ne peuvent plus s'exécuter simultanément. Plus vous les placez vers le bas, plus votre code sera concurrent, mais aussi plus coûteux.
Par ailleurs, il n'y a pratiquement aucune pénalité de performance pour la prise et le relâchement du verrou, il n'y a donc pas lieu de s'inquiéter à ce sujet.