ken*_*ken 12 .net c# multithreading thread-safety null-coalescing-operator
以下代码中是否存在可能导致a的竞争条件NullReferenceException?
- 要么 -
Callback在空合并运算符检查空值之后但在调用函数之前,是否可以将变量设置为null?
class MyClass {
public Action Callback { get; set; }
public void DoCallback() {
(Callback ?? new Action(() => { }))();
}
}
Run Code Online (Sandbox Code Playgroud)
编辑
这是一个出于好奇而产生的问题.我通常不会这样编码.
我并不担心Callback变量变得陈旧.我担心会Exception被抛出DoCallback.
编辑#2
这是我的班级:
class MyClass {
Action Callback { get; set; }
public void DoCallbackCoalesce() {
(Callback ?? new Action(() => { }))();
}
public void DoCallbackIfElse() {
if (null != Callback) Callback();
else new Action(() => { })();
}
}
Run Code Online (Sandbox Code Playgroud)
该方法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
Run Code Online (Sandbox Code Playgroud)
在我看来,call UserQuery+MyClass.get_Callback在使用??操作员时只会被调用一次,但在使用时会被调用两次if...else.难道我做错了什么?
Jon*_*Jon 11
如果我们在编辑澄清时排除了获取过时值的问题,那么null-coalescing选项将始终可靠地工作(即使无法确定确切的行为).替代版本(如果没有null那么称之为)然而不会,并且冒着风险NullReferenceException.
null-coalescing运算符Callback只会被计算一次.代表是不可变的:
组合操作(例如"组合"和"删除")不会更改现有代理.相反,这样的操作返回一个新的委托,其中包含操作的结果,未更改的委托或null.当操作的结果是不引用至少一个方法的委托时,组合操作返回null.当请求的操作无效时,组合操作返回未更改的委托.
另外,委托是引用类型,所以简单的读或写保证是原子的(C#语言规范,第5.5段):
以下数据类型的读取和写入是原子的:bool,char,byte,sbyte,short,ushort,uint,int,float和reference类型.
这确认了空合并运算符无法读取无效值,并且因为只有在不存在错误的情况下才会读取该值.
另一方面,条件版本一次读取委托,然后调用第二次独立读取的结果.如果第一次读取返回非空值但是在null第二次读取发生之前,委托是(原子地,但没有帮助)覆盖,则编译器最终调用Invoke空引用,因此将抛出异常.
所有这些都反映在IL的两种方法中.
在没有相反的明确文件的情况下,是的,这里存在竞争条件,因为在更简单的情况下也存在竞争条件
public int x = 1;
int y = x == 1 ? 1 : 0;
Run Code Online (Sandbox Code Playgroud)
原理是相同的:首先评估条件,然后生成表达式的结果(以后使用).如果发生了导致情况发生变化的事情,那就太晚了.
Dan*_*iel 10
public void DoCallback() {
(Callback ?? new Action(() => { }))();
}
Run Code Online (Sandbox Code Playgroud)
保证相当于:
public void DoCallback() {
Action local = Callback;
if (local == null)
local = new Action(() => { });
local();
}
Run Code Online (Sandbox Code Playgroud)
这是否会导致NullReferenceException取决于内存模型.记录Microsoft .NET框架内存模型永远不会引入额外的读取,因此测试null的值与将调用的值相同,并且您的代码是安全的.但是,ECMA-335 CLI内存模型不那么严格,允许运行时消除局部变量并访问Callback字段两次(我假设它是一个字段或访问一个简单字段的属性).
您应该标记该Callback字段volatile以确保使用正确的内存屏障 - 这使得即使在弱ECMA-335模型中代码也是安全的.
如果它不是性能关键代码,只需使用一个锁(读取回调到锁内部的局部变量就足够了,你不需要在调用委托时保持锁) - 其他任何需要有关内存模型的详细知识才能知道是否它是安全的,确切的细节可能会在未来的.NET版本中发生变化(与Java不同,Microsoft尚未完全指定.NET内存模型).
| 归档时间: |
|
| 查看次数: |
1626 次 |
| 最近记录: |