当前位置: 首页 > 编程语言 > C#

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

时间:2023-04-10 21:21:35 C#

C#中的null合并运算符(??)是线程安全的吗?以下代码中是否存在可能导致NullReferenceException的竞争条件?-或-是否可以在null合并运算符检查null之后但在调用函数之前将Callback变量设置为null?类MyClass{公共操作回调{get;放;}publicvoidDoCallback(){(回调??newAction(()=>{}))();编辑这是出于好奇的问题。我通常不会这样编码。我不担心Callback变量变得陈旧。我担心从DoCallback中抛出异常。编辑#2这是我的课程:classMyClass{ActionCallback{get;放;}publicvoidDoCallbackCoalesce(){(回调??newAction(()=>{}))();}publicvoidDoCallbackIfElse(){if(null!=Callback)Callback();elsenewAction(()=>{})();}}方法DoCallbackIfElse具有可能引发NullReferenceException的竞争条件。DoCallbackCoalesce方法是否具有相同的条件?这是IL输出:MyClass.DoCallbackCoalesce:IL_0000:ldarg.0IL_0001:callUserQuery+MyClass.get_CallbackIL_0006:dupIL_0007:brtrue.sIL_0027IL_0009:popIL_0027IL_0009:popUserQuery+MyClass.get_CallbackIL_0007:brtrue.sIL_0027IL_0009:pop$9__CachedAnonymousMethodDelegate1IL_000F:brtrue.sIL_0022IL_0011:ldnullIL_0012:ldftnUserQuery+MyClass.b__0IL_0018:newobjSystem.Action..ctorIL_001D:stsfldUserQuery+MyClass.CS$9__CachedAnonymousMethodDelegate1IL_0022:ldsfldUserQuery+MyClass.CS$9__CachedAnonymousMethodDelegate1IL_0027:callvirtSystem.Action.InvokeIL_002C:retMyClass.DoCallbackIfElse:IL_0000:ldarg.0IL_0001:callUserQuery+MyClass.get_CallbackIL_0006:brfalse.sIL_0014IL_0008:ldarg.0IL_0009:callUserQuery+MyClass.get_CallbackIL_000E:callvirt系统。Action.InvokeIL_0013:retIL_0014:ldsfldUserQuery+MyClass.CS$9__CachedAnonymousMethodDelegate3IL_0019:brtrue.sIL_002CIL_001B:ldnullIL_001C:ldftnUserQuery+MyClass.b__2IL_0022:newobjSystem.Action..ctorIL_0027:stsfldUserQuery+MyClass.CS$9__CachedAnonymousMethodDelegate3IL_002C:ldsfldUserQuery+MyClass.CS$9__CachedAnonymousMethodDelegate3IL_0031:callvirtSystem.Action.InvokeIL_0036:只需在MyUser中调用get_Callback在使用??运算符,但在使用if...else时使用了两次我做错了什么吗?publicvoidDoCallback(){(回调??newAction(()=>{}))();}保证等同于:publicvoidDoCallback(){Actionlocal=Callback;如果(local==null)local=newAction(()=>{});当地的();这是否会导致NullReferenceException取决于内存模型。记录Microsoft.NETFramework内存模型永远不会引入额外的读取,因此针对null进行的测试与调用的值相同,您的代码是安全的。但是,ECMA-335CLI内存模型不那么严格,允许运行时消除局部变量并访问回调字段两次(我假设它是一个字段或访问一个简单字段的属性)。您应该将Callback字段标记为volatile以确保使用正确的内存屏障——这使得代码即使在弱ECMA-335模型中也是安全的。如果它不是性能关键代码,只需使用锁(对锁内局部变量的读取回调就足够了,调用委托时不需要持有锁)-其他任何东西都需要详细了解内存模型才能知道它是否安全,确切的细节可能会在未来的.NET版本中发生变化(与Java不同,Microsoft尚未完全指定.NET内存模型)。更新如果我们在编辑澄清时排除了获取陈旧值的可能性,那么空合并选项将始终可靠地工作(即使无法确定确切的行为)。备用版本(如果不为空则调用它)但没有,并且有NullReferenceException的风险。空合并运算符使Callback仅被评估一次。委托是不可变的:组合操作如“撰写”和“删除”不会改变现有的委托。相反,这样的操作返回一个包含操作结果的新委托、未更改的委托或null。当操作的结果是一个未引用至少一种方法的委托时,复合操作返回null。当请求的操作无效时,复合操作返回未更改的委托。此外,委托是引用类型,因此保证简单的读取或写入是原语(C#语言规范,第5.5段):读取和写入是以下数据类型的原语:bool、char、byte、sbyte、short、ushort、uint,int,float和引用类型。这证实了null合并运算符无法读取无效值,因为只有在不存在错误的情况下才会读取该值。另一方面,条件版本读取委托一次,然后调用第二次独立读取的结果。如果第一次读取返回一个非空值,但在第二次读取发生之前委托被(primefaces,但没有帮助)覆盖为null,则编译器最终调用空引用上的Invoke,因此将抛出异常。这一切都体现在IL的两个方法上。原始答案如果没有相反的明确文件,那么是的,这里存在竞争条件,因为它也存在于更简单的情况下publicintx=1;整数y=x==1?1:0;基本原理相同:首先评估条件,然后生成表达式的结果(稍后使用)。如果发生什么事情导致情况发生变化,那就太晚了。我在这段代码中没有看到竞争条件。有一些潜在的问题:更简洁的DoCallback写法是:publicvoidDoCallback(){varcallback=Callback;//复制到局部变量是必要的如果Callback为null,它也比原始代码快一点,因为它不创建和调用无操作委托。你可能想用事件替换属性,以获得原语+=和-=:publiceventActionCallback;当在属性上调用+=时,会发生Callback=Callback+someMethod。这不是原始的,因为回调可以在读取和写入之间更改。当在类似事件的字段上调用??+=时,将调用事件的Subscribe方法。对于事件之类的事件,事件订阅保证是原始的。实际上,它使用一些互锁技术来做到这一点。使用空合并运算符??在这里并不重要,它本身也不是线程安全的。重要的是你只读一次回调。还有其他类似的模式涉及??这在任何方面都不是线程安全的。我们假设它是安全的,因为它是一条线?通常情况并非如此。在访问任何共享内存之前,您确实应该使用锁定语句。以上是C#学习教程:C#中的空合并运算符(??)是线程安全的吗?如果所有分享的内容对你有用,需要进一步了解C#学习教程,希望大家多多关注。本文收集自网络,不代表立场。如涉及侵权,请点击右侧联系管理员删除。如需转载请注明出处: