c# - C# 中的空合并运算符 (??) 是线程安全的吗?

标签 c# .net multithreading thread-safety null-coalescing-operator

以下代码中是否存在可能导致 NullReferenceException 的竞争条件?

-- 或--

Callback 变量是否可以在 null 合并运算符检查 null 值之后但在调用函数之前设置为 null?

class MyClass {
    public Action Callback { get; set; }
    public void DoCallback() {
        (Callback ?? new Action(() => { }))();
    }
}

编辑

这是出于好奇而提出的问题。我通常不会这样编码。

我不担心 Callback 变量变得陈旧。我担心 DoCallback 会抛出 Exception

编辑#2

这是我的类(class):

class MyClass {
    Action Callback { get; set; }
    public void DoCallbackCoalesce() {
        (Callback ?? new Action(() => { }))();
    }
    public void DoCallbackIfElse() {
        if (null != Callback) Callback();
        else new Action(() => { })();
    }
}

DoCallbackIfElse 方法存在竞争条件,可能会抛出 NullReferenceExceptionDoCallbackCoalesce 方法是否具有相同的条件?

这是 IL 输出:

MyClass.DoCallbackCoalesce:
IL_0000:  ldarg.0     
IL_0001:  call        UserQuery+MyClass.get_Callback
IL_0006:  dup         
IL_0007:  brtrue.s    IL_0027
IL_0009:  pop         
IL_000A:  ldsfld      UserQuery+MyClass.CS$<>9__CachedAnonymousMethodDelegate1
IL_000F:  brtrue.s    IL_0022
IL_0011:  ldnull      
IL_0012:  ldftn       UserQuery+MyClass.<DoCallbackCoalesce>b__0
IL_0018:  newobj      System.Action..ctor
IL_001D:  stsfld      UserQuery+MyClass.CS$<>9__CachedAnonymousMethodDelegate1
IL_0022:  ldsfld      UserQuery+MyClass.CS$<>9__CachedAnonymousMethodDelegate1
IL_0027:  callvirt    System.Action.Invoke
IL_002C:  ret         

MyClass.DoCallbackIfElse:
IL_0000:  ldarg.0     
IL_0001:  call        UserQuery+MyClass.get_Callback
IL_0006:  brfalse.s   IL_0014
IL_0008:  ldarg.0     
IL_0009:  call        UserQuery+MyClass.get_Callback
IL_000E:  callvirt    System.Action.Invoke
IL_0013:  ret         
IL_0014:  ldsfld      UserQuery+MyClass.CS$<>9__CachedAnonymousMethodDelegate3
IL_0019:  brtrue.s    IL_002C
IL_001B:  ldnull      
IL_001C:  ldftn       UserQuery+MyClass.<DoCallbackIfElse>b__2
IL_0022:  newobj      System.Action..ctor
IL_0027:  stsfld      UserQuery+MyClass.CS$<>9__CachedAnonymousMethodDelegate3
IL_002C:  ldsfld      UserQuery+MyClass.CS$<>9__CachedAnonymousMethodDelegate3
IL_0031:  callvirt    System.Action.Invoke
IL_0036:  ret    

在我看来,call UserQuery+MyClass.get_Callback 在使用 ?? 运算符时只被调用一次,但在使用 if.. .else。我做错了什么吗?

最佳答案

public void DoCallback() {
    (Callback ?? new Action(() => { }))();
}

保证等同于:

public void DoCallback() {
    Action local = Callback;
    if (local == null)
       local = new Action(() => { });
    local();
}

这是否会导致 NullReferenceException 取决于内存模型。 Microsoft .NET 框架内存模型被记录为永远不会引入额外的读取,因此针对 null 测试的值与将被调用的值相同,您的代码是安全的。 但是,ECMA-335 CLI 内存模型不那么严格,允许运行时消除局部变量并访问 Callback 字段两次(我假设它是一个字段或访问一个简单字段的属性).

您应该将 Callback 字段标记为 volatile 以确保使用正确的内存屏障 - 即使在弱 ECMA-335 模型中,这也使代码安全。

如果它不是性能关键代码,只需使用锁(将 Callback 读入锁内的局部变量就足够了,调用委托(delegate)时不需要持有锁)- 其他任何事情都需要有关内存模型的详细知识了解它是否安全,确切的细节可能会在未来的 .NET 版本中发生变化(与 Java 不同,Microsoft 尚未完全指定 .NET 内存模型)。

关于c# - C# 中的空合并运算符 (??) 是线程安全的吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10565838/

相关文章:

c# - 如何将字符串属性绑定(bind)到 ListBox 中的 TextBox

c# - C# 应用程序中的 SQL 查询

c# - 无法在 C# 中将 BsonArray 转换为 BsonDocument

c# - 分割并添加空间

.net - 在 Windows Azure 中托管多版本服务

c++ - c\c++中线程和并发中一本好的编程书籍推荐

c++ - 有什么方法可以将参数传递给线程中的函数?

c# - 温莎容器。 ASP.NET MVC 应用程序中对象的生命周期

c# - 处理 asp.net C#/SQL Server 应用程序的并发问题

java - 使用 wait() 和 notifyAll() 设置和获取方法