zoukankan      html  css  js  c++  java
  • 以线程安全方式引发事件(修正)

      《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方法。(上面代码已修正)

  • 相关阅读:
    Bandicam班迪录屏 高清录制视频软件
    理解WebKit和Chromium: 浏览器综述
    GDAL对于raw数据支持的一个bug
    关于GDAL计算图像坐标的几个问题
    理解WebKit和Chromium: WebKit资源加载机制
    关于web服务器架构的思考
    使用PROJ4库将地心直角坐标(XYZ)转为地心大地坐标(BLH)
    Java数据类型和MySql数据类型对应表
    理解WebKit和Chromium: 基于Chromium内核的Android WebView
    【Unity探究】物理碰撞实验
  • 原文地址:https://www.cnblogs.com/neutra/p/1849721.html
Copyright © 2011-2022 走看看