Follow-up to: Waiting for a lock to release with Thread.Sleep()?
I've found the time I tried to rewrite my WaitForLock
-Method to utilize the Quartz.NET Scheduler which I've been using for some months now for other stuff. It got a little bit more complicated but at least it now misses the dreaded Thread.Sleep()
completely, which is a big improvement for me.
Though, it might now be too complicated.
/// <summary>Waits for the lock to be released within the given timeout.</summary>
/// <param name="lockName">The name of the lock.</param>
/// <param name="timeout">The timeout in seconds.</param>
/// <returns>True if the Lock was released.</returns>
public bool WaitForLock(String lockName, Int32 timeout)
{
// IsLocked(String) does query the database for the status
if(!locker.IsLocked(lockName))
return true;
using(ManualResetEvent reset = new ManualResetEvent(False)
{
ScheduleLockWaiter(lockName, timeout, reset);
reset.WaitOne(timeout * 1000);
// WARNING: This overload is only available in: 4, 3.5 SP1, 3.0 SP2, 2.0 SP2
// I spend a half day trying to figure that out.
}
return !locker.IsLocked(lockName);
}
/// <summary>Create and schedule the job to wait for the lock.</summary>
/// <param name="lockName">The name of the lock.</param>
/// <param name="repeat">The times it shall repeat.</param>
/// <param name="reset">The ManualResetEvent to report on.</param>
private void ScheduleLockWaiter(String lockName, Int32 repeat, ManualResetEvent reset)
{
// Utilizing Quartz.NET
String name = "LockJob_" + lockName;
Trigger trigger = TriggerUtils.MakeSecondlyTrigger("LockTrigger_" + lockName, 1, repeat - 1);
JobDetail job = new JobDetail(name, _lockJobGroup, typeof(LockJob));
job.JobDataMap.Add("Locker", _locker);
job.JobDataMap.Add("Reset", reset);
job.JobDataMap.Add("LockName", lockName);
if(_scheduler.GetJobDetail(name, _lockJobGroup) != null)
_scheduler.UnscheduleJob(name, _lockJobGroup);
_scheduler.ScheduleJob(job, trigger);
}
// Further down the road, our Job-Class
public class LockJob : IJob
{
public void Execute(JobExecutionContext context)
{
ILocker locker = (ILocker)context.JobDetail.JobDataMap.Get("Locker");
ManualResetEvent reset = (ManualResetEvent)context.JobDetail.JobDataMap.Get("reset");
String lockName = (String)context.JobDetail.JobDataMap.Get("LockName");
if(!locker.IsLocked(lockName))
{
context.Scheduler.UndscheduleJob(context.Trigger.Name, context.Trigger.Group);
reset.Set();
}
}
}
I'm not quite sure if I have improved something, or created a beast which will devour me someday. Your thoughts on this?
Update: Since I still have the comment from Brian Reichle in my ears, I've moved on to make the waiting and acquiring of the lock one atomic operation.
Also the scheduled job has changed to directly acquire the Lock. Yes, I know that it tries to lock it twice on success (I just realized that, but did not see a way to change that).
/// <summary>Tries to acquire the Lock within the given timeout.</summary>
/// <param name="lockName">The name of the lock.</param>
/// <param name="timeout">The timeout in seconds.</param>
/// <returns>True if the Lock could be acquired.</returns>
public bool WaitForLock(String lockName, Int32 timeout)
{
// boolean ILocker.Lock(String lockName)
// Returns true if it was able to engage the lock.
if(locker.Lock(lockName))
return true; // Easy way out
using(ManualResetEvent reset = new ManualResetEvent(False)
{
ScheduleLockWaiter(lockName, timeout, reset);
reset.WaitOne(timeout * 1000, false);
}
return locker.Lock(lockName);
}
// Further down the road, our Job-Class
public class LockJob : IJob
{
public void Execute(JobExecutionContext context)
{
ILocker locker = (ILocker)context.JobDetail.JobDataMap.Get("Locker");
ManualResetEvent reset = (ManualResetEvent)context.JobDetail.JobDataMap.Get("reset");
String lockName = (String)context.JobDetail.JobDataMap.Get("LockName");
if(locker.Lock(lockName))
{
context.Scheduler.UndscheduleJob(context.Trigger.Name, context.Trigger.Group);
reset.Set();
}
}
}
Locker
code looks like, but makingWaitForLock
a member of that class (Locker
) would allow it to signal any blocked threads whenever a lock is released. This is probably what Travis had in mind: the purpose ofManualResetEvent
is to provide synchronization without polling. You should post your code for theLocker.Lock
method, it would be easier. \$\endgroup\$