C# 编码事件访问器时的多线程问题

C# 编码事件访问器时的多线程问题,c#,multithreading,events,event-handling,C#,Multithreading,Events,Event Handling,我正在寻找有关Microsoft在编写事件访问器语法时为何建议锁定对象的其他信息。下面显示了Microsoft的代码示例,建议链接到 我理解关于锁定一段代码以控制对它的多线程访问的一般概念,但是,我正在寻找在所示的Microsoft示例的上下文中编写自定义事件访问器逻辑时,这些问题发挥作用的具体原因 下面的示例演示如何实现自定义添加和删除 事件访问器。尽管您可以替换 访问器,我们建议您在添加或 删除新的事件处理程序方法 ~via只有MSDN文章的作者才能为您提供关于文章措辞的明确答案 然而,在我

我正在寻找有关Microsoft在编写事件访问器语法时为何建议锁定对象的其他信息。下面显示了Microsoft的代码示例,建议链接到

我理解关于锁定一段代码以控制对它的多线程访问的一般概念,但是,我正在寻找在所示的Microsoft示例的上下文中编写自定义事件访问器逻辑时,这些问题发挥作用的具体原因

下面的示例演示如何实现自定义添加和删除 事件访问器。尽管您可以替换 访问器,我们建议您在添加或 删除新的事件处理程序方法


~via

只有MSDN文章的作者才能为您提供关于文章措辞的明确答案

然而,在我看来,这个建议的主要原因是代码几乎总是使用编译器提供的事件访问器方法。它们一直都是100%线程安全的,随着编译器最近的变化(我认为是C#4,但我不记得很清楚),它们实际上是100%线程安全的

我认为,使默认实现线程安全的原因是不言而喻的:这样做涉及相当低的成本,而且事件访问器方法中对线程安全的需求非常频繁,以至于在每次需要线程安全时强迫开发人员实现自己的访问器是不合理的

因此,假设默认实现是线程安全的,这意味着事件的使用者(通常不会随时访问事件的源代码)将习惯于假设事件访问器始终是线程安全的。违反这一假设可能会导致代码中出现bug

一句话:如果您100%确定您的事件只能在单个线程中访问,或者至少以线程安全的方式访问,那么您可以在不向访问器方法添加显式线程安全性的情况下逃脱。问题是,达到100%的确定性是可疑的;预测一段特定代码将如何使用几乎是不可能的,尤其是在我们所谈论的未来

代码可以存活很长时间。最好确保它能够处理抛出的内容,尤其是当代码的未来客户端有充分的理由认为代码能够处理它时


旁白:虽然MSDN显示了事件字段本身的锁定,但这对我来说似乎有问题。当字段被更新时,任何当前持有的锁都不会阻止随后执行的代码进入锁,即使锁本身还没有退出。由于对现场的读写顺序错误,某些平台上可能存在现场可见性问题;这可能导致两个随后执行的线程看到锁的不同值,然后并发地进入受保护部分


不要介意使用公共可用值进行锁定这一更普遍的问题。在这个特定的主题上有一些争论,但我倾向于只使用私有值进行锁定。也就是说,不要使用事件字段的当前值锁定(因为它是可更改的),也不要使用此
锁定(因为它是公共的)。

是否可以在引发事件的同时删除事件句柄?我猜这可能会导致空引用异常。我不是100%确定,但我认为这是因为事件基本上是一个多播委托。因此,当您添加/删除处理程序时,实际上会创建一个新的委托。因此,它实际上是一个两步操作:生成新委托和分配给现有变量。如果多个线程同时(取消)订阅,这将为竞争条件引入一个窗口。拥有一个专用的锁对象似乎更明智。@C.Knight-这是一种可能性,尽管它与上述逻辑无关。您应该始终将事件分配给局部变量,然后在尝试调用它之前检查它是否为null。+=不是原子变量。这是一个三步过程,在完成所有三步之前,PreDrawEvent都处于无效状态:(1)将现有PreDrawEvent复制到寄存器中,(2)在寄存器中附加到事件,(3)将新附加的列表复制回变量。。。在这里回答。。。
event EventHandler IDrawingObject.OnDraw
    {
        add
        {
            lock (PreDrawEvent)
            {
                PreDrawEvent += value;
            }
        }
        remove
        {
            lock (PreDrawEvent)
            {
                PreDrawEvent -= value;
            }
        }
    }