The C# language (well, more accurately, the BCL) has the ReaderÂWriterÂLockÂSlim class which has a WaitÂAsync method which returns a task that completes asynchronously when the lock has been acquired. I needed an equivalent for the Parallel Patterns Library (PPL), and since I couldn’t find one, I ended up writing one. (If you can find one, please let me know!)
// AsyncUILock is a nonrecursive lock that can be waited on
// asynchronously from a UI thread.
class AsyncUILock
{
public:
Concurrency::task<void> WaitAsync()
{
std::lock_guard<std::mutex> guard(mutex);
if (!locked) {
// Lock is available. Acquire it.
locked = true;
return completed_apartment_aware_task();
}
// Lock is not available.
return completed_apartment_aware_task()
.then([captured_completion = completion] {
// Wait for it to become available.
return Concurrency::create_task(captured_completion);
}).then([this] {
// Then try again.
return WaitAsync();
});
}
// void Release()
// {
// std::lock_guard<std::mutex> guard(mutex);
// locked = false;
// auto previousCompletion = completion;
// completion = Concurrency::task_completion_event<void>();
// previousCompletion.set();
// }
void Release()
{
[&] {
// See follow-up article
std::lock_guard<std::mutex> guard(mutex);
locked = false;
return std::exchange(completion, {});
}().set();
}
private:
std::mutex mutex;
bool locked = false;
Concurrency::task_completion_event<void> completion;
};
The object consists of a std::mutex which protects the internal state, a flag that indicates whether the object has been claimed, and a task completion event that we use to signal anybody waiting on the lock that they should check again.
Update: We signal the completion event after dropping the lock.
I could have used an SRWLock instead of a std::mutex, but I was lazy and wanted to take advantage of the existing std::lock_guard.
You can perform async waits on this object in the usual manner. For example:
AsyncUILock lock;
void DoSomething()
{
lock.WaitAsync().then([]{
// do something with the lock held.
lock.Release();
});
}
or if you prefer co_await (and you probably do):
AsyncUILock lock;
void DoSomething()
{
co_await lock.WaitAsync();
// do something with the lock held.
lock.Release();
}
At this point, you might decide to return an RAII type to ensure that the lock doesn’t leak. I’ll leave that as an exercise.
0 comments