提前道歉:这个问题来自一个试图学习高级 C# 的硬核、未改革的C++开发人员。请考虑以下事项:
if (myUserDefinedObject != null)
{
myUserDefinedObject.ToString();
}
这显然不是线程安全的。另一方面,我看过两个教程说?。(空条件运算符或"猫王运算符")例如,
myUserDefinedObject?.ToString();
线程安全。除非编译器在幕后(颤抖)包裹着 [mutex?] 锁,否则我不明白这怎么可能是真的。如果这个成语是线程安全的,有人可以指出我如何实现的技术描述吗?如果它不是线程安全的,有没有人有一个参考实际上说它不是?
澄清BJ Myers的(正确)答案。
在 C# 中,可以将事件视为委托类型的字段 - 就像可以将属性视为属性类型的字段一样 - 并且该"字段"的值可以为 null。如果您不幸在一个线程上修改了事件处理程序,而另一个线程正在尝试调用它,则可能会遇到以下情况:
if (this.SomeEvent != null)
this.SomeEvent( ... );
不是线程安全的。该值可能会发生变化,以便在检查之前为非 null,在检查后为 null,并且程序崩溃。
使这个"线程安全"的常用方法(我建议使用该术语)是将值复制到本地,然后测试本地的 null。 这样做的好处是不会因空取消引用而崩溃。但是,聪明的开发人员会注意到还有一场比赛!序列可以是
- 缓存在线程 A 上的非空事件处理程序
- 事件处理程序在线程 B 上设置为 null
- 事件处理程序所需的状态在线程 B 上被销毁
- 事件处理程序在线程 A 上运行并严重死亡
所以从这个意义上说,这种模式不是"线程安全的"。 如果您处于这种不幸的位置,您有责任确保实现适当的线程逻辑,以便不会发生这种情况。 你可以随心所欲地这样做。如果你想要能够在一个线程上调用事件处理程序,同时在另一个线程上改变事件,那么你必须付费以确保它的安全,或者处理竞争条件错误。
我个人会像瘟疫一样避免这种情况,但我不够聪明,无法编写正确的多线程代码。
现在,关于实际问题:
some_expression ?. ToString();
与
temp = some_expression
temp == null ? null : temp.ToString()
您认为后一种代码是"线程安全的"吗?
来自 MSDN(强调我的):
null 条件成员访问的另一个用途是以线程安全的方式调用委托,代码要少得多。旧方法需要如下代码:
var handler = this.PropertyChanged; if (handler != null) handler(…)
新方法要简单得多:
PropertyChanged?.Invoke(e)
新方法是线程安全的,因为编译器生成代码来仅计算一次 PropertyChanged,将结果保留在临时变量中。
因此,这里不涉及锁定 - 线程安全是通过创建一个局部临时变量来强制执行的,这可以防止不同的线程在 null 检查和其他一些操作之间修改该变量。