Lock and load
Everyone knows how to do locking in C#, right?
155 private void Notify(AtomPacket packet)
156 {
157 List<CommandListener> listeners; //copy so we don't need the lock long
158 lock (_commandListeners)
159 listeners = new List<CommandListener>(_commandListeners.Values);
160 foreach (var listener in listeners)
161 listener.Offer(packet);
162 }
Sample 1 – locking in C# with the lock statement
The point of the argument to the lock statement is to scope the lock to the argument. The lock statement is syntactic sugar for use of Monitor. Without it, the code would look like this:
155 private void Notify(AtomPacket packet)
156 {
157 List<CommandListener> listeners; //copy so we don't need the lock long
158 Monitor.Enter(_commandListeners);
159 try
160 {
161 listeners = new List<CommandListener>(_commandListeners.Values);
162 }
163 finally
164 {
165 Monitor.Exit(_commandListeners);
166 }
167 foreach (var listener in listeners)
168 listener.Offer(packet);
169 }
Sample 2 – locking in C# using Monitor directly
The problem with Monitor is that it makes no distinction between read and write locks. Very often in server scenarios there is frequent reading and rare writing of collections of session state objects. The read operations can and should run concurrently, with threads waiting only for update operations.
To this end, Microsoft provides the ReaderWriterLock class, and its lightweight, high-performance sibling ReaderWriterLockSlim, which is faster and simpler but isn’t intended for recursive lock entry on the same thread.
Here’s a sample from MSDN demonstrating the use of ReaderWriterLockSlim:
1 using System;
2 using System.Threading;
3 using System.Collections.Generic;
4
5 public class SynchronizedCache
6 {
7 private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();
8 private Dictionary<int, string> innerCache = new Dictionary<int, string>();
9
10 public string Read(int key)
11 {
12 cacheLock.EnterReadLock();
13 try
14 {
15 return innerCache[key];
16 }
17 finally
18 {
19 cacheLock.ExitReadLock();
20 }
21 }
22
23 public void Add(int key, string value)
24 {
25 cacheLock.EnterWriteLock();
26 try
27 {
28 innerCache.Add(key, value);
29 }
30 finally
31 {
32 cacheLock.ExitWriteLock();
33 }
34 }
35
36 public bool AddWithTimeout(int key, string value, int timeout)
37 {
38 if (cacheLock.TryEnterWriteLock(timeout))
39 {
40 try
41 {
42 innerCache.Add(key, value);
43 }
44 finally
45 {
46 cacheLock.ExitWriteLock();
47 }
48 return true;
49 }
50 else
51 {
52 return false;
53 }
54 }
55
56 public AddOrUpdateStatus AddOrUpdate(int key, string value)
57 {
58 cacheLock.EnterUpgradeableReadLock();
59 try
60 {
61 string result = null;
62 if (innerCache.TryGetValue(key, out result))
63 {
64 if (result == value)
65 {
66 return AddOrUpdateStatus.Unchanged;
67 }
68 else
69 {
70 cacheLock.EnterWriteLock();
71 try
72 {
73 innerCache[key] = value;
74 }
75 finally
76 {
77 cacheLock.ExitWriteLock();
78 }
79 return AddOrUpdateStatus.Updated;
80 }
81 }
82 else
83 {
84 cacheLock.EnterWriteLock();
85 try
86 {
87 innerCache.Add(key, value);
88 }
89 finally
90 {
91 cacheLock.ExitWriteLock();
92 }
93 return AddOrUpdateStatus.Added;
94 }
95 }
96 finally
97 {
98 cacheLock.ExitUpgradeableReadLock();
99 }
100 }
101
102 public void Delete(int key)
103 {
104 cacheLock.EnterWriteLock();
105 try
106 {
107 innerCache.Remove(key);
108 }
109 finally
110 {
111 cacheLock.ExitWriteLock();
112 }
113 }
114
115 public enum AddOrUpdateStatus
116 {
117 Added,
118 Updated,
119 Unchanged
120 };
121 }
Sample 3 – use of ReaderWriterLockSlim (courtesy MSDN)
Notice that the scope of the lock is the instance of ReaderWriterLockSlim, which is much less useful, especially in server code which needs scoping. You could keep a dictionary of lock objects, but then you’d need to lock that. So I wrote ReadWriteMonitor, which is a static class that emulates Monitor but using ReaderWriterLockSlim.
1 using System;
2 using System.Collections.Generic;
3 using System.Threading;
4
5 namespace Locksmith
6 {
7 public static class ReadWriteMonitor
8 {
9 static Dictionary<object, ReaderWriterLockSlim> _locks = new Dictionary<object, ReaderWriterLockSlim>();
10 static ReaderWriterLockSlim _locksLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
11
12 private static ReaderWriterLockSlim GetReaderWriterLock(object obj)
13 {
14 try
15 {
16 _locksLock.EnterUpgradeableReadLock();
17 if (!_locks.ContainsKey(obj))
18 {
19 _locksLock.EnterWriteLock();
20 _locks.Add(obj, new ReaderWriterLockSlim());
21 }
22 return _locks[obj];
23 }
24 finally
25 {
26 _locksLock.ExitUpgradeableReadLock();
27 if (_locksLock.IsWriteLockHeld) _locksLock.ExitWriteLock();
28 }
29 }
30
31 public static void EnterReadLock(object obj)
32 {
33 ReaderWriterLockSlim rwl = GetReaderWriterLock(obj);
34 rwl.EnterReadLock();
35 }
36
37 public static void ExitReadLock(object obj)
38 {
39 ReaderWriterLockSlim rwl = GetReaderWriterLock(obj);
40 rwl.ExitReadLock();
41 }
42
43 public static void EnterUpgradeableReadLock(object obj)
44 {
45 ReaderWriterLockSlim rwl = GetReaderWriterLock(obj);
46 rwl.EnterUpgradeableReadLock();
47 }
48
49 public static void ExitUpgradeableReadLock(object obj)
50 {
51 ReaderWriterLockSlim rwl = GetReaderWriterLock(obj);
52 rwl.ExitUpgradeableReadLock();
53 }
54
55 public static void EnterWriteLock(object obj)
56 {
57 ReaderWriterLockSlim rwl = GetReaderWriterLock(obj);
58 rwl.EnterWriteLock();
59 }
60
61 public static void ExitWriteLock(object obj)
62 {
63 ReaderWriterLockSlim rwl = GetReaderWriterLock(obj);
64 rwl.ExitWriteLock();
65 }
66
67 public static void FreeLock(object obj)
68 {
69 try
70 {
71 _locksLock.EnterUpgradeableReadLock();
72 if (_locks.ContainsKey(obj))
73 {
74 _locksLock.EnterWriteLock();
75 ReaderWriterLockSlim rwl = _locks[obj];
76 if ((rwl.WaitingReadCount == 0) && (rwl.WaitingUpgradeCount == 0) && (rwl.WaitingWriteCount == 0))
77 {
78 _locks.Remove(obj);
79 }
80 else
81 {
82 throw new InvalidOperationException("ReaderWriterLockSlim object cannot be freed because locks are still held");
83 }
84 }
85 }
86 finally
87 {
88 _locksLock.ExitUpgradeableReadLock();
89 if (_locksLock.IsWriteLockHeld)
90 _locksLock.ExitWriteLock();
91 }
92
93 }
94
95 public static bool IsUpgradeableLockHeld(object obj)
96 {
97 ReaderWriterLockSlim rwl = GetReaderWriterLock(obj);
98 return rwl.IsUpgradeableReadLockHeld;
99 }
100
101 public static bool IsWriteLockHeld(object obj)
102 {
103 ReaderWriterLockSlim rwl = GetReaderWriterLock(obj);
104 return rwl.IsWriteLockHeld;
105 }
106 }
107 }
Sample 4 - ReadWriteMonitor
You use it very much as in the sample for direct use of Monitor, excepting of course that you have a choice of three types of lock: read, upgradeable and write.
155 private void Notify(AtomPacket packet)
156 {
157 List<CommandListener> listeners; //copy so we don't need the lock long
158 ReadWriteMonitor.EnterReadLock(_commandListeners);
159 try
160 {
161 listeners = new List<CommandListener>(_commandListeners.Values);
162 }
163 finally
164 {
165 ReadWriteMonitor.ExitReadLock(_commandListeners);
166 }
167 foreach (var listener in listeners)
168 listener.Offer(packet);
169 }
Sample 5 – sample 2 rewritten to use ReadWriteMonitor read locks
Broadly speaking, an upgradeable read lock imposes higher overheads for the sake of slightly different semantics that are better if you do upgrade (it affects the sequence in which write lock requests are queued). If you know you won’t upgrade, as in the above sample, then upgradeability is a waste of time in every sense. For more information regarding these modes and their use see ReaderWriterLockSlim on MSDN or in Visual Studio Help.
ReadWriteMonitor itself provides a good example of conditions under which you would use an upgradeable read lock, even if it (obviously) uses ReaderWriterLockSlim directly. _locks is a hash table of ReaderWriterLockSlim instances keyed on obj. _locksLock is the locking object for _locks. We use an upgradeable read lock to determine whether there is a locking object for obj. If there is, we return it and release the upgradeable read lock. If there isn’t, we upgrade to a write lock, create a new instance of ReaderWriterLockSlim and add it to the collection, then return it and release the write lock.
12 private static ReaderWriterLockSlim GetReaderWriterLock(object obj)
13 {
14 try
15 {
16 _locksLock.EnterUpgradeableReadLock();
17 if (!_locks.ContainsKey(obj))
18 {
19 _locksLock.EnterWriteLock();
20 _locks.Add(obj, new ReaderWriterLockSlim());
21 }
22 return _locks[obj];
23 }
24 finally
25 {
26 _locksLock.ExitUpgradeableReadLock();
27 if (_locksLock.IsWriteLockHeld) _locksLock.ExitWriteLock();
28 }
29 }
Sample 6 – appropriate use of upgradeable locks