Let's say I have a form with two buttons (button1
and button2
) and a resource object (r
). The resource has its own locking and unlocking code to handle concurrency. The resource could be modified by any thread.
When button1
is clicked, its handler does some modifying of r
itself and then calls _IndependentResourceModifierAsync()
asynchronously which does some modifying of r
in a spawned task. _IndependentResourceModifierAsync()
acquires r
's lock before doing this. Also because the handler is messing with r
itself, it acquires r
's lock too.
When button2
is clicked, it just calls _IndependentResourceModifierAsync()
directly. It does no locking itself.
As you know, the handlers for the buttons will always execute on the main thread (except for the spawned Task
).
There's two things that I want to guarantee:
- If either
button1
orbutton2
is clicked while the resource is locked by the main thread, an exception will be thrown. (Can't use aMonitor
orMutex
because they are thread driven) - The nesting of locks from
button1_Click()
through_IndependentResourceModiferAsync()
should not cause a deadlock. (Can't use aSemaphore
).
Basically, I think what I'm looking for is a "stack-based lock" if such a thing exists or is even possible. Because when an async method continues after an await, it restores stack state. I did a lot of searching for anyone else who has had this problem but came up dry. That likely means I'm over-complicating things, but I am curious what people have to say about it. There might be something really obvious I'm missing. Many thanks.
public class Resource
{
public bool TryLock();
public void Lock();
public void Unlock();
...
}
public class MainForm : Form
{
private Resource r;
private async void button1_Click(object sender, EventArgs e)
{
if (!r.TryLock())
throw InvalidOperationException("Resource already acquired");
try
{
//Mess with r here... then call another procedure that messes with r independently.
await _IndependentResourceModiferAsync();
}
finally
{
r.Unlock();
}
}
private async void button2_Click(object sender, EventArgs e)
{
await _IndependentResourceModifierAsync();
}
private async void _IndependentResourceModiferAsync()
{
//This procedure needs to check the lock too because he can be called independently
if (!r.TryLock())
throw InvalidOperationException("Resource already acquired");
try
{
await Task.Factory.StartNew(new Action(() => {
// Mess around with R for a long time.
}));
}
finally
{
r.Unlock();
}
}
}