我正在读一本有关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对象是指同一个对象,无需更改任何内容-结果将对这两个对象都产生影响。谢谢!
答案 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