首先,让我解释一下情况......
我有一个有2个属性的类:DataA和DataB;它们是什么并不重要,重要的是每个都可以从另一个计算出来。我处于多线程环境中,并希望在必要时计算DataA / DataB(并非总是会访问这两者)。我的第一个想法是...... ...
public SomeDataObject DataA
{
get
{
if (dataAisAvailable)
{
return dataA;
}
else
{
if (dataBisAvailable)
{
lock (dataACalcLock)
{
// Don't want other threads recalculating dataA
if (dataAisAvailable)
{
return dataA;
}
////////////////////////////////
// Calculate dataA from dataB //
////////////////////////////////
dataAisAvailable = true;
return dataA;
}
}
else
{
return null;
}
}
}
}
使用这种模式,假设dataB确实可用,到达场景的第一个线程(让我们称之为thread1)进入并计算dataA,到目前为止一切都很好......但是,任何一直在等待的线程要计算的dataA现在必须逐个访问...不是非常有效的imho。我想让这些线程等待thread1完成计算dataA,然后所有人都为它做好准备,好像没有锁定。
我可能对ManualResetEvents有其他想法,但我不确定如何安全地确保一个且正好一个线程通过来进行计算。
我希望我已经清楚地解释过了,虽然我不相信我有。很高兴澄清......
编辑:我的不好,我在.NET 4.0上。这是在Silverlight ......
答案 0 :(得分:2)
这听起来很像发明ReaderWriterLockSlim
的场景(允许多个并发读取器,而一次只允许一个线程写入)。
它可能看起来像这样的东西(没有正确验证这个代码,所以如果你决定使用它,请确保它按你想要的那样工作):
private ReaderWriterLockSlim dataLock = new ReaderWriterLockSlim();
public SomeDataObject DataA
{
get
{
if (dataAisAvailable)
{
return dataA;
}
dataLock.EnterReadLock();
try
{
if (dataBisAvailable)
{
dataLock.EnterUpgradeableReadLock();
try
{
// Don't want other threads recalculating dataA
if (dataAisAvailable)
{
return dataA;
}
dataLock.EnterWriteLock();
try
{
////////////////////////////////
// Calculate dataA from dataB //
////////////////////////////////
dataAisAvailable = true;
}
finally
{
dataLock.ExitWriteLock();
}
return dataA;
}
finally
{
dataLock.ExitUpgradeableReadLock();
}
}
else
{
return null;
}
}
finally
{
dataLock.EnterReadLock();
}
}
}
提示/插件:如果你想减少添加的try / finally结构的数量,你可以在扩展方法中包含一些(as presented in my blog),或者将它包装在IDisposable
代理(as suggested by Josh Perry)中可能更干净。
答案 1 :(得分:1)
受到对我的问题的评论的启发,后来我被删除了,我检查了Lazy<>
,然后选择了类似的内容......
static SomeDataObject DefaultData;
private Lazy<SomeDataObject> dataA = new Lazy<SomeDataObject>(() => DefaultData, LazyThreadSafetyMode.ExecutionAndPublication);
private Lazy<SomeDataObject> dataB = new Lazy<SomeDataObject>(() => DefaultData, LazyThreadSafetyMode.ExecutionAndPublication);
public SomeDataObject DataA
{
get
{
return dataA.Value;
}
set
{
dataA = new Lazy<SomeDataObject>(() => value, LazyThreadSafetyMode.ExecutionAndPublication);
dataB = new Lazy<SomeDataObject>(GetDataB, LazyThreadSafetyMode.ExecutionAndPublication);
}
}
public SomeDataObject DataB
{
get
{
return dataB.Value;
}
set
{
dataB = new Lazy<SomeDataObject>(() => value, LazyThreadSafetyMode.ExecutionAndPublication);
dataA = new Lazy<SomeDataObject>(GetDataA, LazyThreadSafetyMode.ExecutionAndPublication);
}
}
private SomeDataObject GetDataA()
{
if (DefaultData == dataB.Value)
{
return null;
}
////////////////////////////////
// Calculate dataA from dataB //
// and return it. //
////////////////////////////////
}
private SomeDataObject GetDataB()
{
if (DefaultData == dataA.Value)
{
return null;
}
////////////////////////////////
// Calculate dataA from dataB //
// and return it. //
////////////////////////////////
}
不幸的是,在Silverlight中没有ReaderWriterSlimLock,否则看起来很有希望。一点基准测试表明,上述内容远比我自己编写的任何内容更快(更不用说做我想要的更多)了。
答案 2 :(得分:0)
建议:
if (dataAisAvailable)
{
//Wait for AutoResetEvent here, perhaps add a timeout and when it expires, you can return the current dataA, so threads don't wait forever.
return dataA;
}
else
{
if (dataBisAvailable)
{
lock (dataACalcLock)
{
// Don't want other threads recalculating dataA
if (dataAisAvailable)
{
return dataA;
}
////////////////////////////////
// Calculate dataA from dataB //
////////////////////////////////
dataAisAvailable = true;
//Set AutoResetEvent to signalled so waiting threads can get to DataA.
return dataA;
}
}
答案 3 :(得分:0)
确定 - 您想要做的是避免重新计算A和B,同时在第一次计算后访问时避免锁定,是吗?
如果一个线程读取dataAisAvailable并发现它为true,那么就没有问题 - 线程可以使用A.如果它读取dataAisAvailable并发现它为false,那么就会出现问题并需要获取一个独占锁来确保dataAisAvailable仍为false,如果是,则计算它。我想,一个关键部分会做。如果A / B需要很长时间来计算,这将回退到内核锁定,但这只会在线程第一次发现其中一个布尔值为假时发生。
我认为你可以逃脱这一点,因为布尔人只能“单向”,从假到真,所以你可以通过一个简单的布尔检查,(我认为)。
您的'主题1'会发现dataAisAvailable为false,因此尝试获取锁定。如果成功,它会在锁内再次检查dataAisAvailable并计算或不计算。然后它退出锁,返回dataA。如果'thread 2'首先进入,在线程1检查dataAisAvailable和线程1获取锁定之间,计算dataA并退出锁定,则线程1将进入锁定,发现dataAisAvailable现在为true,因此只需使用dataA退出锁定
线程2-N将始终将dataAisAvailable视为true并获得A而不尝试获取锁。
RGDS, 马丁