|
|
1
7
锁定的目的是在覆盖默认事件连接时维护线程安全。如果其中一些解释了你已经能够从乔恩的文章中推断出来的事情,我道歉;我只是想确保我对所有事情都完全清楚。 如果您像这样声明您的事件:
然后,对事件的订阅将自动与
然而 ,如果您决定覆盖默认事件,即:
现在你有了一个问题,因为已经没有隐锁了。您的事件处理程序刚刚失去线程安全。这就是你需要使用锁的原因:
就我个人而言,我不在乎这个,但乔恩的理论是合理的。但是,我们确实有
轻微的
问题。如果你用的是私人的
这是坏的 ,因为 我们正在访问同一个私有存储字段,而不使用属性使用的同一个锁 . 如果类外部的一些代码变为:
这个通过公共财产的外部代码是为了维护这个锁。但是 你不是 因为你是在接触私人领域。
这个
变量赋值
的一部分
更新
事实证明,一些围绕评论的对话是正确的,正如OP的更新所证明的那样。这本身不是扩展方法的问题,而是委托具有值类型语义并在分配时得到复制。即使你拿了
你 可以 用一个静态实用方法来绕过这个限制(或者特性,取决于您的视角),尽管我很肯定您 不能 使用扩展方法。下面是一个可以工作的静态方法:
这个版本是有效的,因为我们实际上没有通过
(如前所述,您必须确保通过与
|
|
|
2
2
我意识到我不是在回答你的问题,但是在引发事件时消除空引用异常可能性的一个简单方法是在声明的站点将所有事件设置为委托。例如:
|
|
|
3
2
在C中,新的最佳实践是:
|
|
|
4
1
像引用这样的基本类型的赋值是原子的,所以这里没有使用锁的意义。 |
|
5
1
“线程安全”事件可能变得相当复杂。您可能会遇到一些不同的问题: 空引用异常最后一个订户可以取消订阅空检查和调用委托之间的订阅,从而导致NullReferenceException。这是一个非常简单的解决方案,您可以锁定调用站点(这不是一个好主意,因为您正在调用外部代码)
复制处理程序(推荐)
或者拥有一个始终订阅的空委托。
注意,选项2(复制处理程序)不需要锁——因为副本是原子的,所以不可能存在不一致性。 要将其恢复到扩展方法中,您需要对选项2做一些细微的更改。您的副本是在调用扩展方法时发生的,因此您可以只使用以下方法:
可能存在一个抖动问题,即内联和删除临时变量。我有限的理解是它对于<.NET 2.0或ECMA标准是有效的行为,.NET 2.0+增强了保证,使其成为无问题的单声道YMMV。 陈旧数据好吧,我们已经通过复制处理程序解决了nre问题。现在,我们有了第二个过时数据问题。如果订户在我们之间取消订阅,获取副本并调用委托,那么我们仍然会调用它们。可以说,这是不正确的。选项1(锁定调用站点)解决了这个问题,但存在死锁的风险。我们有点陷入困境——我们有两个不同的问题,需要为同一段代码提供两个不同的解决方案。 由于死锁很难诊断和预防,建议使用选项2。这就要求 被叫者 必须处理被调用,即使在取消订阅之后。它应该足够简单,以便处理程序检查它是否仍然希望/能够被调用,如果不希望/能够被调用,则可以完全退出。 好吧,那为什么乔恩·斯基特建议锁在一个通风口里?他正在阻止缓存读取成为过时数据的原因。对lock的调用转换为monitor.enter/exit,这两个函数都会生成一个内存屏障,防止对读/写和缓存数据重新排序。出于我们的目的,它们本质上使委托易变——这意味着它不能缓存在CPU寄存器中,每次都必须从主内存中读取更新后的值。这可以防止订阅服务器取消订阅的问题,但事件的值被从不注意的线程缓存。 结论那么,你的代码呢:
好吧,您要复制一个委托(实际上是两次),然后执行一个产生内存屏障的锁。不幸的是,您的锁是在复制本地副本时被占用的,这对于jon skeet试图解决的过时数据问题没有任何作用。您需要这样的东西:
在我看来,这并不是真正的代码。 |
|
|
6
0
这里有很多问题,我会一次解决一个。 问题1:您的代码,是否需要锁定?首先,您所讨论的代码,不需要锁定该代码。 换句话说,RAISE方法可以简单地重写为:
这样做的原因是委托是一个不可变的构造,这意味着一旦进入该方法,进入该方法的委托在该方法的生命周期内不会改变。 即使不同的线程与事件同时发生干扰,也会产生新的委托。对象中的委托对象将不会更改。 这就是问题1,如果您像以前那样拥有代码,是否需要锁定?答案是否定的。 问题3:为什么最后一段代码的输出没有改变?这可以回到上面的代码。扩展方法已收到其委托的副本,此副本将永远不会更改。“使其更改”的唯一方法是不向方法传递副本,而是像这里的其他答案中所示,为包含委托的字段/变量传递别名。只有这样你才能观察到变化。 你可以这样看待这个问题:
在这种情况下,您希望Y变为5吗?不,可能不是,代表也是这样。 问题3:为什么jon在他的代码中使用了锁?为什么乔恩用锁 post: Choosing What To Lock On ?好吧,他的代码不同于您的代码,因为他没有在任何地方传递底层委托的副本。 在他的代码中,如下所示:
如果他不使用锁,而是这样编写代码:
然后,不同的线程可以在表达式计算之间更改“handler”,以确定是否有任何订阅服务器,以及实际调用,换句话说:
如果他通过了
基本上,将委托值作为参数传递的行为会生成副本,这个“副本”代码是原子代码。如果您对一个不同的线程计时正确,那么该不同的线程要么及时进行更改,以便通过调用获得新的值,要么不进行更改。 即使在调用时使用锁的一个原因可能是引入了内存屏障,但我怀疑这会对代码产生任何影响。 这就是问题3,为什么乔恩的代码实际上需要锁。 问题4:更改默认事件访问器方法怎么样?问题4在这里的其他答案中提到,它围绕着在事件上重写默认的添加/删除访问器时需要锁定的问题,以便出于任何原因控制逻辑。 基本上,不是这样:
你想写下这个,或者它的一些变体:
本代码 做 需要锁定,因为如果您查看原始实现,而不使用重写的访问器方法,您会注意到它默认使用锁定,而我们编写的代码则不使用锁定。 我们可能会以这样的执行场景结束(请记住,“a+=b”实际上意味着“a=a+b”):
要解决这个问题,您需要锁定。 |
|
|
7
-2
我不相信为了避免空值而复制副本的有效性。当所有订阅服务器 告诉你的学生不要和他们说话。 零均值 没人想听你的活动。 也许监听对象已经被释放了。在这种情况下,复制处理程序只是将问题转移到。现在,您可以调用一个试图取消订阅事件的事件处理程序,而不是调用一个空值。调用复制的处理程序只会将问题从发布服务器移动到订阅服务器。 我的建议就是试着抓住它;
我还想看看微软是如何提出最重要的事件的:点击一个按钮。他们只是在基地做这个
所以,他们复制处理程序,但不锁定它。 |
|
|
JLosc · Powershell脚本因文件锁定而失败 2 年前 |
|
|
Sriram Umapthy · PostgreSql行级锁 2 年前 |
|
|
a a · 为什么在这个可重入锁示例中需要引用计数? 4 年前 |
|
|
JohnLBevan · 为什么原子语句上需要锁提示? 8 年前 |