32

提前道歉:这个问题来自一个试图学习高级 C# 的铁杆、未改革的 C++ 开发人员。考虑以下:

if (myUserDefinedObject != null)
{
    myUserDefinedObject.ToString();
}

这显然不是线程安全的。另一方面,我看过两个说 ? 的教程。(空条件运算符或“猫王运算符”)例如,

myUserDefinedObject?.ToString();

线程安全的。除非编译器在封面下包裹了一个 [mutex?] 锁(颤抖),否则我不明白这怎么可能是真的。如果这个成语是线程安全的,有人可以指出我是如何完成的技术描述吗?如果它不是线程安全的,是否有人有参考实际上说它不是?

4

2 回答 2

38

我想澄清 BJ Myers 的(正确)答案。

在 C# 中,可以将事件视为委托类型的字段——就像可以将属性视为属性类型的字段一样——并且该“字段”的值可以为空。如果您不幸在一个线程上修改了事件处理程序,而另一个线程正在尝试调用它,您可能会遇到以下情况:

if (this.SomeEvent != null) 
    this.SomeEvent( ... );

不是线程安全的。该值可以被改变,使其在检查之前为非空,在检查之后为空,并且程序崩溃。

使这个“线程安全”的通常方法,我建议使用这个术语,是将值复制到本地,然后测试本地是否为空。这样做的好处是不会因空取消引用而崩溃。然而,聪明的开发者会注意到,还有一场比赛!序列可以是

  • 缓存在线程 A 上的非空事件处理程序
  • 线程 B 上的事件处理程序设置为 null
  • 事件处理程序所需的状态在线程 B 上被销毁
  • 事件处理程序在线程 A 上运行并可怕地死去

所以从这个意义上说,这种模式不是“线程安全的”。如果您处于这种不幸的境地,您有责任确保实施适当的线程逻辑,以免发生这种情况。你可以随心所欲地这样做。如果您想要能够在一个线程上调用事件处理程序同时在另一个线程上改变事件的(有问题的)好处,那么您必须支付使其安全或处理竞争条件错误的费用。

我个人会像瘟疫一样避免这种情况,但我不够聪明,无法编写正确的多线程代码。

现在,至于实际问题:

some_expression ?. ToString();

是相同的

temp = some_expression
temp == null ? null : temp.ToString()

您认为后一种代码是“线程安全的”吗?

于 2016-03-04T01:37:06.420 回答
31

来自MSDN(重点是我的):

空条件成员访问的另一个用途是以线程安全的方式调用委托,代码少得多。旧方式需要如下代码:

var handler = this.PropertyChanged;
if (handler != null)
    handler(…)

新方法要简单得多:

PropertyChanged?.Invoke(e)

新方法是线程安全的,因为编译器生成的代码只评估一次 PropertyChanged,将结果保存在临时变量中。

所以这里不涉及锁定——线程安全是通过创建一个局部临时变量来强制执行的,这可以防止不同的线程在空检查和其他一些操作之间修改该变量。

于 2016-03-03T23:50:35.880 回答