13.4 对锁和字段风格的事件的微小改变
13.4.1 健壮的锁
1 class Program 2 { 3 static object locker = new object(); 4 static void Main(string[] args) 5 { 6 List<string> list = new List<string>(); 7 lock (locker) 8 { 9 list.Add("item"); 10 } 11 12 //在C# 4之前——包括使用C# 4处理.NET 4之前的东西时——以上语句将被有效地编译为下面 的代码: 13 14 object tmp = locker; 15 Monitor.Enter(tmp); 16 try 17 { 18 list.Add("item"); 19 } 20 finally 21 { 22 Monitor.Exit(tmp); 23 } 24 } 25 }
这没有问题,并且它还避免了一些问题。我们要确保释放的监视器与获取的是同一个,因此
首先将被锁定内容的引用复制到一个临时局部变量内 。这同时意味着锁的表达式只会进行一
次求值。然后我们在 try 语句块之前获取锁。因此如果获取锁的线程异常终止,则不会执行
finally 块中释放锁的语句。这还将导致另一个问题:如果线程在获取锁之后和进入 try 块之前
异常终止,我们也无法释放锁。这可能会导致死锁——其他线程将一直等待该线程释放锁。尽管
CLR一直以来都在努力阻止类似事情发生,但也不是完全没有可能发生。
我们所需要的,是一种原子地获取锁并知道它已经被获取的方式。幸运的是,.NET 4新增加
了 Monitor.Enter 的重载,C# 4的编译器将使用这种方式:
1 class Program 2 { 3 static object locker = new object(); 4 static void Main(string[] args) 5 { 6 List<string> list = new List<string>(); 7 object tmp = locker; 8 9 bool acquired = false; 10 try 11 { 12 Monitor.Enter(tmp, ref acquired); 13 list.Add("item"); 14 } 15 finally 16 { 17 if (acquired) 18 { 19 Monitor.Exit(tmp); 20 } 21 } 22 } 23 }
13.4.2 字段风格的事件
值得简单一提的是,C# 4对字段风格事件的实现方式作了两处修改。尽管它们是潜在的破坏性更改,但似乎不会对你产生什么影响。
总之,字段风格的事件像字段一样进行声明,不再包含显式的 add/remove 语句块,如下:
public event EventHandler Click;
首先,线程安全的实现方式发生了改变。在C# 4之前,字段风格的事件生成的代码锁定的是
this (实例事件)或声明事件的类型(静态事件)。而C# 4中,编译器实现了线程安全,对原子
的订阅和退订使用了 Interlocked.CompareExchange<T> 。与之前对 lock 语句的修改不同,
面对旧版本的.NET Framework时,这项更改同样适用。
其次,在声明事件的类中,事件名称的含义改变了。以前,在声明事件的类中订阅(或退订)
事件——如 Click += DefaultClickHandler; ——将直接使用后台字段,完全跳过 add/remove
实现。现在情况变了,使用 += 或 -= 时,事件的名称就指向事件本身,而不再是后台字段。当名
称用于其他意图时(通常为分配或调用),则仍然指向后台字段。
尽管在平时使用时你可能不会注意这两处改变,不过它们是合理的,可以使一切变得整洁。
Chris Burrows在他的博客中深入研究了这个话题,想了解更多内容可以参考http://mng.bz/Kyr4。