I've just started using Nito.AsyncEx package and AsyncLock
instead of a normal lock() { ... }
section where I have async
calls within the locked section (since you can't use lock()
in such cases for good reasons I've just read about). This is within a job that I'm running from Hangfire. Let's call this the 'worker' thread.
In another thread, from an ASP.NET controller, I'd like to check if there's a thread that's currently executing within the locked section. If there's no thread in the locked section then I'll schedule a background job via Hangfire. If there's already a thread in the locked section then I don't want to schedule another one. (Yes, this might sound a little weird, but that's another story).
Is there a way to check this using the Nito.AsyncEx
objects, or should I just set a flag at the start of the locked section and unset it at the end?
e.g. I'd like to this:
public async Task DoAJobInTheBackground(string queueName, int someParam)
{
// do other stuff...
// Ensure I'm the only job in this section
using (await _asyncLock.LockAsync())
{
await _aService.CallSomethingAsync());
}
// do other stuff...
}
and from a service called by a controller use my imaginary method IsSomeoneInThereNow()
:
public void ScheduleAJobUnlessOneIsRunning(string queueName, int someParam)
{
if (!_asyncLock.IsSomeoneInThereNow())
{
_backgroundJobClient.Enqueue<MyJob>(x =>
x.DoAJobInTheBackground(queueName, someParam));
}
}
but so far I can only see how to do this with a separate variable (imagining _isAnybodyInHere
is a thread-safe bool or I used Interlocked
instead):
public async Task DoAJobInTheBackground(string queueName, int someParam)
{
// do other stuff...
// Ensure I'm the only job in this section
using (await _asyncLock.LockAsync())
{
try
{
_isAnybodyInHere = true;
await _aService.CallSomethingAsync());
}
finally
{
_isAnybodyInHere = false;
}
}
// do other stuff...
}
and from a service called by a controller:
public void ScheduleAJobUnlessOneIsRunning(string queueName, int someParam)
{
if (!_isAnybodyInHere)
{
_backgroundJobClient.Enqueue<MyJob>(x =>
x.DoAJobInTheBackground(queueName, someParam));
}
}
Really it feels like there should be a better way. The AsyncLock
doc says:
You can call Lock or LockAsync with an already-cancelled CancellationToken to attempt to acquire the AsyncLock immediately without actually entering the wait queue.
but I don't understand how to do that, at least using the synchronous Lock
method.
I don't understand how to do that
You can create a new CancellationToken
and pass true
to create one that is already canceled:
using (_asyncLock.Lock(new CancellationToken(canceled: true)))
{
...
}
The call to Lock
will throw if the lock is already held.
That said, I don't think this is a good solution to your problem. There's always the possibility that the background job is just about to finish, the controller checks the lock and determines it's held, and then the background job releases the lock. In that case, the controller will not trigger a background job.