gpt4 book ai didi

C# 事件和线程安全

转载 作者:IT王子 更新时间:2023-10-29 03:29:09 25 4
gpt4 key购买 nike

更新

从 C# 6 开始,the answer这个问题是:

SomeEvent?.Invoke(this, e);

我经常听到/阅读以下建议:

在检查 null 之前,请务必制作事件的副本并开火。这将消除事件变为 null 的线程处理的潜在问题。在检查 null 和触发事件的位置之间的位置:
// Copy the event delegate before checking/calling
EventHandler copy = TheEvent;

if (copy != null)
copy(this, EventArgs.Empty); // Call any handlers on the copied list

更新:我从阅读有关优化的文章中想到,这可能还需要事件成员是可变的,但 Jon Skeet 在他的回答中指出,CLR 不会优化副本。

但与此同时,为了使这个问题甚至发生,另一个线程必须这样做:
// Better delist from event - don't want our handler called from now on:
otherObject.TheEvent -= OnTheEvent;
// Good, now we can be certain that OnTheEvent will not run...

实际序列可能是这种混合物:
// Copy the event delegate before checking/calling
EventHandler copy = TheEvent;

// Better delist from event - don't want our handler called from now on:
otherObject.TheEvent -= OnTheEvent;
// Good, now we can be certain that OnTheEvent will not run...

if (copy != null)
copy(this, EventArgs.Empty); // Call any handlers on the copied list

关键是 OnTheEvent在作者取消订阅后运行,但他们只是专门取消订阅以避免这种情况发生。当然,真正需要的是在 add 中具有适当同步的自定义事件实现。和 remove访问器(accessor)。此外,如果在触发事件时持有锁,则可能会出现死锁问题。

这也是 Cargo Cult Programming ?似乎是这样 - 很多人必须采取这一步来保护他们的代码免受多线程的影响,而实际上在我看来,事件在它们可以用作多线程设计的一部分之前需要比这更加小心.因此,那些没有特别注意的人可能会忽略这个建议——这对于单线程程序来说根本不是问题,事实上,考虑到 volatile 的缺失。在大多数在线示例代码中,建议可能根本不起作用。

(并且在成员声明中分配空的 delegate { } 这样您就不需要首先检查 null 不是更简单吗?)

更新:如果不清楚,我确实掌握了建议的意图 - 在所有情况下都避免空引用异常。我的观点是,这个特殊的空引用异常只有在另一个线程从事件中删除时才会发生,这样做的唯一原因是确保不会通过该事件接收到进一步的调用,这显然不是通过这种技术实现的.你会隐瞒比赛条件 - 最好揭露它!该空异常有助于检测对组件的滥用。如果您希望您的组件免受滥用,您可以按照 WPF 的示例 - 将线程 ID 存储在您的构造函数中,然后在另一个线程尝试直接与您的组件交互时抛出异常。或者实现一个真正的线程安全组件(不是一件容易的事)。

所以我认为,仅仅执行这种复制/检查习语是对 cargo 崇拜的编程,给你的代码添加困惑和噪音。要真正防止其他线程需要做更多的工作。

更新 Eric Lippert 的博客文章:

所以我错过了关于事件处理程序的一个主要事情:“即使在取消订阅事件之后,事件处理程序也必须在被调用时保持健壮”,显然因此我们只需要关心事件的可能性代表是 null .是否在任何地方记录了对事件处理程序的要求?

所以:“还有其他方法可以解决这个问题;例如,将处理程序初始化为一个永远不会被删除的空操作。但进行空检查是标准模式。”

所以我的问题剩下的一个片段是,为什么显式空检查是“标准模式”?另一种方法是分配空委托(delegate),只需要 = delegate {}被添加到事件声明中,这消除了在每个事件发生的地方的那些小堆臭仪式。很容易确保实例化空委托(delegate)的成本很低。或者我还缺少什么?

肯定是这样(正如 Jon Skeet 所建议的)这只是 .NET 1.x 建议并没有消失,就像它在 2005 年应该做的那样?

最佳答案

由于条件的原因,不允许 JIT 执行您在第一部分中讨论的优化。我知道这是不久前作为幽灵提出的,但这是无效的。 (我不久前与乔达菲或万斯莫里森核对过;我不记得是哪个了。)

如果没有 volatile 修饰符,所获取的本地副本可能会过时,但仅此而已。它不会导致 NullReferenceException .

是的,肯定存在竞争条件——但总会有的。假设我们只是将代码更改为:

TheEvent(this, EventArgs.Empty);

现在假设该委托(delegate)的调用列表有 1000 个条目。完全有可能在另一个线程取消订阅列表末尾附近的处理程序之前,列表开头的操作已经执行。但是,该处理程序仍将被执行,因为它将是一个新列表。 (代表是不可变的。)据我所知,这是不可避免的。

使用空委托(delegate)当然可以避免无效检查,但不能解决竞争条件。它也不保证您总是“看到”变量的最新值。

关于C# 事件和线程安全,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/786383/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com