最近做项目,发现一些线程方法异常,查半天没有发现什么的地方能引发这些异常。具体看下面列子。
public class A
A a = new A();
A b =a;
object.ReferenceEquals(a, b)是返回true。
如果把a =null; 那么b是不是也是 null?
其实不然,a赋值为null,只是a指向了一个null,b还是指向的 newA();如图所示:
string mA="abc";
string mB=“abc”;
object.ReferenceEquals(mA, mB);这个函数的结果是 false还是true?
答案是true。
原因是 字符串是不可改变类型?
what?听都没听过上面是不可改变类型。
ok,接下来我们来一起探讨以下上面叫不可改变类型;
imutable ? what's meaning?(an object is immutable if its state doesn’t change once the object has been created);
意思是对象一旦被创建,则对象的状态不可改变,如字符串, string mA=“abc”;那么 abc一旦被创建,则abc所有状态不可改变,如长度,字符串有的状态都不可改变了,那么 mB=“abc”,实际上两个abc是同一个,所以mA和mB引用同一个对象。
上面讲了不可改变,那跟delegate和event有毛关系.啊?
不急上菜;
Delegates are immutable reference types.
Delegate is not delegate,But delegate is Delegate,请注意d有大小写的区分
看代码:
c# delegate 关键字
( public delegate void TestDelegate(string msg).
private TestDelegate test;
test.GetType() ==MulticastDelegate);
编译该代码,并查看中间语言,有如下类型:
我们看到delegate声明的变量 会编译产生 MulticastDelegate,而MulticastDelegate是集成Delegate的,这下是不是明白许多了,本身c#只提供delegate关键字给你用。
而且我们还看到delegate生成的中间语言(IL)中自动产生了如下方法:
Invoke()
BeginInvoke()
EndInvoke()
这些方法在Delegate中并没有,why?
最主要的原因在于编译器对delegate生成的类,是有签名的,发现没 实例化一个delegate总是要传入一个方法的引用。
而Delegate是不需要的。
下面这个图显示delegate的操作,加、减运算后的结果,注意一定要看懂 后你就明白大部分了:
p.instanceMethod(string msg)
{
return msg;
}
d1、d2、d3 = new TestDelegate(p.instanceMethod);意思是有d1,d2,d3这三个委托,
[d1, d2, d3] - [d2, d1] =? 问题是 如果 有一个委托是 d1+d2+d3, 另一个是d2+d1,他们相减结果等于多少?参考如下图:
答案是 :[d1,d2,d3],为什么呢,因为在[d1,d2,d3]中找不到有[d2, d1]的出现,c#中delegate实际是用一个列表进行存储的,这个操作如果匹配不到子序列,相减就失败。
ok,委托我们讲解到这里了,接下来谈一谈事件,我们写事件一般会想下面这样写;
private MyEventHandler __MyEvent;
public event MyEventHandler MyEvent
{
add
{
lock (this)
{
this.__MyEvent += value;
}
}
remove
{
lock (this)
{
this.__MyEvent -= value;
}
}
}
这样如果不知道细节,看不出来有任何问题,如果在细细想想,可能会发现问题;是因为lock的是this,这是改类的实例,如果其他地方需要等待该对象,或者其他地方也lock了,有可能造成是死锁。
解决方法:用改类的一个object来lock,如object lockObject= new object()。 lock(lockObject)
在调用事件时候我们一般会这样调用:
if (this.MyEvent != null)
{
this.MyEvent(this, args);
}
这样调用实际上有一个线程安全问题;
if another thread unsubscribes from the event after the if statement but before the event is raised, then this code may result in a NullReferenceException!
这句话的意思是 假如别的线程在 if (this.MyEvent != null)语句执行后, this.MyEvent(this, args)还为执行,其他线程对this.MyEvent作了其他操作如减掉该委托可能等于null,
那么执行 this.MyEvent(this, args)时候可能会引发 空异常。 所以有时候我们看代码 看不出来这里会抛异常,其实如果知道这些细节我们还是能挖掘出来这些问题。
那怎么解决这个问题呢?
有没有想起我们上面提到 delegate是 不可改变类型,这个时候有大用了,不可改变意思是new了后就不可变了, 想 加 减操作实际上是重新 new了新的 对象计算后在赋值的。
如上面的[d1] +[d2] =[d1,d2],这里有三个对象。
只需要这样就可以解决上面的问题;
MyEventHandler myEvent = this.MyEvent;
if (myEvent != null)
{
myEvent(this, args);
}
this solution does prevent the NullReferenceException race condition; but it introduces another race condition in its place;
这个解决上面的问题,但是他带来了另外一个问题,就是当MyEvent在MyEventHandler myEvent = this.MyEvent执行后改变了,那么myEvent 执行的是改变之前的函数。
不过这种情况 比上面的 要好多了,及时MyEvent在MyEventHandler myEvent = this.MyEvent执行后改变了,我也能保证了 myEvent的执行。
参考文献:
http://csharpindepth.com/Articles/Chapter2/Events.aspx
http://www.codeproject.com/Articles/37474/Threadsafe-Events
第一次写中文章,估计表达的不太清楚,还望批评指正