以下代码中是否存在可能导致 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
方法存在竞争条件,可能会抛出 NullReferenceException
。 DoCallbackCoalesce
方法是否具有相同的条件?
这是 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/