我有一个多用户可以调用的进程。这是一个非常昂贵的查询,但它应该只需要每隔5分钟左右运行一次,以刷新一些归档数据。我现在有一个锁,这样我们就不会一次运行几次进程,这会使系统瘫痪,但每个用户都必须等待之前的锁运行才能运行。如果有3-4个用户在等待锁,那么第4个用户必须等待20分钟以上才能运行他们的查询。
我想要做的是锁定这个对象,并在第一个请求时运行查询。如果有任何其他请求进入,让它们等待当前锁完成,然后返回,而不实际执行查询。
.Net中有没有内置的东西可以实现这一点,或者我需要为这个锁编写一些特定的代码?
发布于 2013-06-18 05:04:26
你可以用一个ManualResetEvent和一个锁来做这件事。
private object _dataLock = new object();
private ManualResetEvent _dataEvent = new ManualResetEvent(false);
private ArchiveData GetData()
{
if (Monitor.TryEnter(_dataLock))
{
_dataEvent.Reset(); // makes other threads wait on the data
// perform the query
// then set event to say that data is available
_dataEvent.Set();
try
{
return data;
}
finally
{
Monitor.Exit(_dataLock);
}
}
// Other threads wait on the event before returning data.
_dataEvent.WaitOne();
return data;
}因此,第一个到达那里的线程获得锁并清除_dataEvent,这表明其他线程将不得不等待数据。这里有一个竞争条件,如果第二个客户端在_dataEvent重置之前到达那里,它将返回旧数据。我认为这是可以接受的,考虑到它是存档数据,而且发生这种情况的机会窗口非常小。
其他线程试图获取锁,但都失败了,并被WaitOne阻塞。
当数据可用时,执行查询的线程设置事件,释放锁,并返回数据。
请注意,我并没有将整个锁体放在try...finally中。有关原因,请参阅Eric Lippert的Locks and Exceptions do not mix。
发布于 2016-06-21 19:34:22
这个解决方案是为那些不能接受多个调用者执行“准备代码”的可能性的人准备的。
这种技术避免了在“正常”用例场景中使用锁,因为数据已经准备好了。锁定确实有一些开销。这可能适用于您的用例,也可能不适用。
这种模式被称为if-lock-if模式。我已经尽我最大的能力对内联进行了注释:
bool dataReady;
string data;
object lock = new object();
void GetData()
{
// The first if-check will only allow a few through.
// Normally maybe only one, but when there's a race condition
// there might be more of them that enters the if-block.
// After the data is ready though the callers will never go into the block,
// thus avoids the 'expensive' lock.
if (!dataReady)
{
// The first callers that all detected that there where no data now
// competes for the lock. But, only one can take it. The other ones
// will have to wait before they can enter.
Monitor.Enter(lock);
try
{
// We know that only one caller at the time is running this code
// but, since all of the callers waiting for the lock eventually
// will get here, we have to check if the data is still not ready.
// The data could have been prepared by the previous caller,
// making it unnecessary for the next callers to enter.
if (!dataReady)
{
// The first caller that gets through can now prepare and
// get the data, so that it is available for all callers.
// Only the first caller that gets the lock will execute this code.
data = "Example data";
// Since the data has now been prepared we must tell any other callers that
// the data is ready. We do this by setting the
// dataReady flag to true.
Console.WriteLine("Data prepared!");
dataReady = true;
}
}
finally
{
// This is done in try/finally to ensure that an equal amount of
// Monitor.Exit() and Monitor.Enter() calls are executed.
// Which is important - to avoid any callers being left outside the lock.
Monitor.Exit(lock);
}
}
// This is the part of the code that eventually all callers will execute,
// as soon as the first caller into the lock has prepared the data for the others.
Console.WriteLine("Data is: '{0}'", data);
}MSDN参考:
https://stackoverflow.com/questions/17154732
复制相似问题