gpt4 book ai didi

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

转载 作者:可可西里 更新时间:2023-11-01 08:18:27 28 4
gpt4 key购买 nike

以下代码中是否存在可能导致 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/

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