我正在尝试为我的个人项目开发一些异步方法,我正在研究框架以供参考。
我已经下载了.NET source code以更仔细地查看位和螺栓(使用开发人员评论,Reflector没有给我们的东西:-P)
无论如何,在很多.NET类中,我遇到了以下模式:
class SomeType
{
// ...
SomeClass m_Field;
// ...
SomeClass SomeMethod()
{
SomeClass localField = m_Field;
if (localField == null)
{
localField = new SomeClass();
m_Field = localField;
}
return localField;
}
}
这让我想知道使用这种模式有什么好处?
据我所知,上述模式比下面的模式更糟糕,性能更好:
class SomeType
{
// ...
SomeClass m_Field;
// ...
SomeClass SomeMethod()
{
if (m_Field == null)
{
m_Field = new SomeClass();
}
return m_Field;
}
}
或者我在这里遗漏了什么?
答案 0 :(得分:2)
在许多情况下,差异纯粹是审美和主观的,但想到一个与另一个的三个理由浮现在脑海中:
线程安全:无锁算法可能需要担心这一点,但如果所有同步都是通过锁完成的,则不应该是一个问题。
性能:在某些情况下可能稍微快一些,但老实说我怀疑它会在大多数情况下产生影响。
异常安全:通常,您需要小心将中间更改放入本地,然后仅在操作完成后将结果发布到字段,而不会引发异常。这充当了一种事务机制,因为没有人会看到只有一半字段设置的对象。
答案 1 :(得分:2)
这种方法可能有助于保护您免受从一个线程调用SomeMethod的情况,但在您检查m_Field为null之后,控制权将传递给另一个线程,该线程将其设置为null。然后控制返回到第一个线程,但它仍然认为m_Field!= null,这可能会导致NullReferenceException
据我记得,Richter的“CLR via C#”在有关事件的章节中有几句话。
答案 2 :(得分:0)
可能只是提示编译器应该将字段读入寄存器而不是在内存中重复访问它。没有理由说第一个版本应该比你列出的第二个版本更差性能更好。它与编译器生成的代码几乎完全相同。将对象字段读入寄存器,测试它是否为null,根据需要进行修改,然后将其写回内存中的对象字段。
答案 3 :(得分:0)
MS .NET JIT执行register allocation。对于这种简单的情况,该临时变量最终应该保存在寄存器中,而不是堆栈中。生成的x86字节代码应该比非类型情况下读取两次(一次检查null,一次返回)的类成员运行得更快,对于null情况也更快。
代码生成器通常必须在发生时立即写入 objects 中的字段的更改,并在每次引用时从对象字段读取,否则在某些情况下,某个线程可能 never 看到对另一个线程所做对象的更改,反之亦然。
但是如果使用 local 变量,编译器会假定它不必将更改写入局部变量(或者甚至将其存储在堆栈中),因为访问另一个线程的本地变量不是C#允许。