只能调用一次的安全方法?
我试图编写一个线程安全的方法,它只能被调用一次(每个对象实例)。 如果之前被调用过,应抛出异常。
我提出了两个解决方案。 他们都是对的吗? 如果没有,他们有什么问题?
lock
:
public void Foo()
{
lock (fooLock)
{
if (fooCalled) throw new InvalidOperationException();
fooCalled = true;
}
…
}
private object fooLock = new object();
private bool fooCalled;
通过Interlocked.CompareExchange
:
public void Foo()
{
if (Interlocked.CompareExchange(ref fooCalled, 1, 0) == 1)
throw new InvalidOperationException();
…
}
private int fooCalled;
如果我没有弄错,这个解决方案的优点是无锁(这在我的情况下似乎不相关),并且它需要更少的私有领域。
如果有更好的方法,我也愿意提供合理的意见,哪些解决方案应该是首选的,并提出进一步的建议。
您的Interlocked.CompareExchange
解决方案看起来最好,并且(如您所说)是无锁的。 它也比其他解决方案复杂得多。 锁相当重量级,而CompareExchange
可以编译为单个CAS cpu指令。 我说去那个。
双重检查锁定模式就是你所追求的:
这是你之后的事情:
class Foo
{
private object someLock = new object();
private object someFlag = false;
void SomeMethod()
{
// to prevent locking on subsequent calls
if(someFlag)
throw new Exception();
// to make sure only one thread can change the contents of someFlag
lock(someLock)
{
if(someFlag)
throw new Exception();
someFlag = true;
}
//execute your code
}
}
一般来说,当遇到类似这样的问题时,请尝试遵循上述类似的知识模式。
这使得它可以被识别并且不易出错,因为当你遵循一个模式时,你不太可能错过某些东西,特别是当涉及到线程时。
在你的情况下,第一个如果没有多大意义,但通常你会想要执行实际的逻辑,然后设置标志。 当你执行你的代码(可能代价很高)时,第二个线程会被阻塞。
关于第二个示例:
是的,这是正确的,但不要让它比现在更复杂。 你应该有很好的理由不使用简单的锁定,在这种情况下,它会使代码更加复杂(因为Interlocked.CompareExchange()
是不为人知的)而没有实现任何东西(正如你指出锁定少一些锁定来设置一个布尔值国旗在这种情况下并不是真正的好处)。
Task task = new Task((Action)(() => { Console.WriteLine("Called!"); }));
public void Foo()
{
task.Start();
}
public void Bar()
{
Foo();
Foo();//this line will throws different exceptions depends on
//whether task in progress or task has already been completed
}
链接地址: http://www.djcxy.com/p/58005.html