以线程安全方式引发事件(修正)

  《CLR via C#》3rd中提到,应该以线程安全的方式引发事件,不禁冒冷汗,一直以来还真没注意到这个问题,以前写的不少代码得重新审查修正了。下面是引用原文说明:

.Net Framework最初发布时,是建议开发者用以下方式引发事件:

 
  
protected virtual void OnNewMail(NewMailEventArgs e)
{
if (NewMail != null ) NewMail( this , e);
}

这个OnNewMail方法的问题在于,线程可能发现NewMail不为null,然后,就在调用NewMail之前,另一个线程从委托链中移除了一个委托,是NewMail变成了null。这会造成抛出一个NullReferenceException异常。

  于是我写了以下代码测试重现这种线程竞态的情况: 

代码
 
   
using System;
using System.Threading;
using System.Diagnostics;

namespace Neutra.Utils
{
class EventTest
{
public event EventHandler MyEvent;

static void Main( string [] args)
{
Console.WriteLine(
" Test1 start " );
Test1();
Console.WriteLine(
" Test1 end " );

Console.WriteLine(
" Test2 start " );
Test2();
Console.WriteLine(
" Test2 end " );
}

static void AddAndRemoveEventHandler( object obj)
{
var instance
= obj as EventTest;
for ( int i = 0 ; i < 100000 ; i ++ )
{
instance.MyEvent
+= HandleEvent;
Thread.Sleep(
0 );
instance.MyEvent
-= HandleEvent;
Thread.Sleep(
0 );
}
}

static void HandleEvent( object sender, EventArgs e)
{
Console.Write(
' > ' );
}

static void Test1()
{
var sw
= Stopwatch.StartNew();
var instance
= new EventTest();
Thread thread
= new Thread(AddAndRemoveEventHandler);
thread.Start(instance);
int i = 0 ;
try
{
for (i = 0 ; i < 2000 ; i ++ )
{
if (instance.MyEvent != null )
{
instance.MyEvent(instance, EventArgs.Empty);
}
Thread.Sleep(
0 );
}
Console.WriteLine();
}
catch (Exception exception)
{
sw.Stop();
Console.WriteLine();
Console.WriteLine(
" index = {0}, time: {1} " , i, sw.Elapsed);
Console.WriteLine(exception);
}
finally
{
thread.Abort();
thread.Join();
}
}

static void Test2()
{
var sw
= Stopwatch.StartNew();
var instance
= new EventTest();
Thread thread
= new Thread(AddAndRemoveEventHandler);
thread.Start(instance);
int i = 0 ;
try
{
for (i = 0 ; i < 2000 ; i ++ )
{
var handler
= Interlocked.CompareExchange( ref instance.MyEvent, null , null );
if (handler != null )
{
handler(instance, EventArgs.Empty);
}
Thread.Sleep(
0 );
}
Console.WriteLine();
}
catch (Exception exception)
{
sw.Stop();
Console.WriteLine();
Console.WriteLine(
" index = {0}, time: {1} " , i, sw.Elapsed);
Console.WriteLine(exception);
}
finally
{
thread.Abort();
thread.Join();
}
}
}
}

 

  我测试了好几次,index最小的一次是60多,最大的1000多,并发问题还是比较明显的。下面是其中一次测试结果:

  有些人倾向于使用EventHandler handler = instance.MyEvent;代替使用Interlocked.CompareExchange方法,书中也提到了,这种方式也是可行的,因为MS的JIT编译器不会将这里的handler优化掉。书中最后说道“另外由于事件主要在单线程的情形中使用(WinForm/WPF/SilverLight),所以线程安全并不是一个问题。”

  我认为,这个问题还是有必要注意一下的。这种问题一般都很难重现,而且还是该死的NullReferenceException异常,一看上下文代码,霎时间还真是“莫名其妙”,最后归于人品问题倒是相当无奈了。

===============================================================

今天发现代码中有误,Interlocked.Exchange会交换两引用,应该使用Interlocked.CompareExchange方法。(上面代码已修正)

转载于:https://www.cnblogs.com/neutra/archive/2010/10/13/1849721.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值