是C#6?(Elvis op)线程安全吗?如果是这样,怎么样?

Dic*_*ges 26 c# c#-6.0 null-conditional-operator

提前道歉:这个问题来自一个试图学习高级C#的硬核,未经过改造的C++开发人员.考虑以下:

if (myUserDefinedObject != null)
{
    myUserDefinedObject.ToString();
}
Run Code Online (Sandbox Code Playgroud)

这显然不是线程安全的.另一方面,我看过两个教程说?(例如,Null条件运算符或'Elvis运算符')

myUserDefinedObject?.ToString();
Run Code Online (Sandbox Code Playgroud)

IS线程安全.除非编译器将[mutex?]锁定在它下面(颤抖),否则我不明白这是怎么回事.如果这个成语是线程安全的,有人可以指点我如何完成它的技术描述?如果它不是线程安全的,那么有没有人有一个实际上说它不是的参考?

Eri*_*ert 33

我想澄清BJ迈尔斯的(正确的)答案.

在C#中,事件可以被认为是委托类型的字段 - 正如属性可以被认为是属性类型的字段 - 并且该"字段"的值可以为空.如果你在一个线程上修改事件处理程序而另一个线程试图调用它的情况很糟糕,你可以进入以下情况:

if (this.SomeEvent != null) 
    this.SomeEvent( ... );
Run Code Online (Sandbox Code Playgroud)

不是线程安全的.该值可以被突变,以便在检查之前为非null,在检查之后为null,并且程序崩溃.

使这个"线程安全"的常用方法,我使用的术语是将值复制到本地,然后测试本地为null.这样做的好处是不会因空取消引用而崩溃.然而,聪明的开发人员会注意到还有一场比赛!顺序可以是

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

所以从这个意义上讲,这种模式不是"线程安全的".如果您处于这种不幸的位置,您有责任确保实施适当的线程逻辑,以便不会发生这种情况.你可以随心所欲地做到这一点.如果你想要在一个线程上调用一个事件处理程序同时在另一个线程上改变事件的(可疑的)好处,那么你必须付钱以使其安全,或者处理竞争条件错误.

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

现在,至于实际问题:

some_expression ?. ToString();
Run Code Online (Sandbox Code Playgroud)

是相同的

temp = some_expression
temp == null ? null : temp.ToString()
Run Code Online (Sandbox Code Playgroud)

你认为后一个代码是"线程安全的"吗?

  • @Narvalex:我怎么知道?解释*你在哪里放锁*,以及*什么种族的锁保护*,*你的策略是什么,以避免死锁*,然后我们可以有一些确定你的解决方案是否正确.请记住,*线程安全是整个程序*的属性,因此如果您想知道程序是否是线程安全的,您必须提供整个程序的精度.*这就是为什么线程很难*.你拿着一块砖头问"这块砖是用这种砖做的,结构合理吗?" 这是房子的财产,而不是砖块. (8认同)
  • @Narvalex:那是对的; 在初始读取确定本地不为空之后,不应引入第二次读取.但这只是你问题的开始,而不是它们的结束.假设它不是null*并且你调用它*,但*原始*变量*已被取消.现在你正在调用一个已被删除*的处理程序*,但删除它的代码可能会被这个事实感到惊讶!如果您处于这种情况,那么*每个人都需要合作*以确保不会发生这种情况,或者如果发生这种情况,那么陈旧的处理程序不会崩溃. (5认同)

BJ *_*ers 27

来自MSDN(强调我的):

null条件成员访问的另一个用途是以线程安全的方式使用更少的代码调用委托.旧方法需要如下代码:

var handler = this.PropertyChanged;
if (handler != null)
    handler(…)
Run Code Online (Sandbox Code Playgroud)

新方法更简单:

PropertyChanged?.Invoke(e)
Run Code Online (Sandbox Code Playgroud)

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

因此,这里不涉及锁定 - 通过创建本地临时变量来强制执行线程安全性,这可以防止不同的线程在null检查和其他操作之间修改该变量.

  • 因此,它的线程安全程度取决于您想要做什么.如果另一个线程调用Dispose(),您可能会得到一个ObjectDisposedException,但是您不会在调用站点获得空指针异常. (7认同)
  • 这不是真正的线程安全,因为[在此解释](https://msdn.microsoft.com/en-us/magazine/jj883956.aspx)请参阅"阅读简介"部分.JIT有时可以完全消除局部变量并将另一个读取引入实际字段. (3认同)