如果我将对事件对象的引用复制到另一个对象,然后再更改事件对象该怎么办?

时间:2019-03-24 09:15:45

标签: c# .net events delegates clr

我正在读一本有关C#和CLR的书,无法理解一件事。文本如下:

  

要解决此竞争状况,许多开发人员如下编写OnNewMail方法:

// Version 2
protected virtual void OnNewMail(NewMailEventArgs e) {
    EventHandler<NewMailEventArgs> temp = NewMail;
    if (temp != null) temp(this, e);
}
  

这里的想法是将对NewMail的引用复制到一个临时变量temp中,该临时变量引用执行分配时的委托链。现在,此方法比较temp和null并调用temp,因此分配给temp后另一个线程是否更改NewMail无关紧要。请记住,代表是不可变的,这就是为什么这种技术在理论上可行的原因。

所以,问题是:为什么要工作?我认为temp和NewMail对象是指同一个对象,无需更改任何内容-结果将对这两个对象都产生影响。谢谢!

2 个答案:

答案 0 :(得分:1)

尤其是CLR中的委托类型和MulticastDelegate类型的委托类型,尽管它们是引用类型,但属于称为“不可变”类型的罕见类型组。这意味着对于此类类型的引用分配操作会创建实例的副本,这与仅复制引用值的常规引用类型的分配不同。所以当你写:

EventHandler<NewMailEventArgs> temp = NewMail;

创建由NewMail引用的委托的新副本,并将对该副本的引用分配给temp变量,因此在执行此行之后,将有{{1} }委托:EventHandler<NewMailEventArgs>引用的实例和NewMail引用的另一个实例(不是您可能想到的由两个变量引用的单个实例)。这就是为什么现在您可以安全地调用temp所指向的委托的原因,因为在调用委托的一段时间内,另一个线程无法将其清空。

答案 1 :(得分:0)

一种避免并发问题的防弹方法是使用System.Threading.Volatile.Read方法。临时变量可以通过未记录的编译器优化来删除。目前它可以正常工作,但将来可能会改变。

using System;
using System.Threading;

namespace EventHandling
{
    class Program
    {
        static void Main(string[] args)
        {
            var eventProvider = new EventProvider();
            eventProvider.Event += (sender, e) => Console.WriteLine("Event fired");
            eventProvider.FireEvent();
        }
    }

    class EventProvider
    {
        public event EventHandler Event;

        protected void OnEvent(EventArgs e) => Volatile.Read(ref Event)?.Invoke(this, e);

        public void FireEvent() => OnEvent(EventArgs.Empty);
    }
}

要了解并发如何影响事件处理,可以尝试以下代码:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace EventHandling
{
    class Program
    {
        static void Main(string[] args)
        {
            while(true)
                new Program().Run();
        }

        private void Run()
        {
            var eventProvider = new EventProvider();
            eventProvider.Event += HandleEvent;
            Console.WriteLine("subscribed");

            var unsubscribe = new Task(() =>
            {
                eventProvider.Event -= HandleEvent;
                Console.WriteLine("unsubscribed");
            });

            var fireEvent = new Task(() => eventProvider.FireEvent());

            fireEvent.Start();
            unsubscribe.Start();

            Task.WaitAll(fireEvent, unsubscribe);

            Console.ReadLine();
        }

        private void HandleEvent(object sender, EventArgs e) => Console.WriteLine("Event fired");


    }

    class EventProvider
    {
        public event EventHandler Event;

        protected void OnEvent(EventArgs e)
        {
            var temp = Volatile.Read(ref Event);
            Console.WriteLine("temp delegate created");
            Thread.Sleep(25); // time to unsubscribe concurrently

            if (temp != null)
            {
                Console.WriteLine("temp delegate invoking");
                temp.Invoke(this, e);
                Console.WriteLine("temp delegate invoked");
            }
            else
                Console.WriteLine("temp delegate is empty");
        }

        public void FireEvent() => OnEvent(EventArgs.Empty);
    }
}

有时输出为:

subscribed
unsubscribed
temp delegate created
temp delegate is empty

有时:

subscribed
temp delegate created
unsubscribed
temp delegate invoking
Event fired
temp delegate invoked