I have a service that ensures that exactly one popup is displayed at the same time. AddPopupAsync
can be called concurrently, i.e. a popup is open while another 10 AddPopupAsync
requests drop in. The code:
public async Task<int> AddPopupAsync(Message message)
{
//[...]
while (popupQueue.Count != 0)
{
await popupQueue.Peek();
}
return await Popup(interaction);
}
But I can spot two unwanted things that can happen because of the lack of thread safety:
Popup method works with a TaskCompletionSource
and before calling its SetResult
method, popupQueue.Dequeue()
is called.
I think about using the atomic TryPeek
from ConcurrentQueue
in order to make #1 thread safe:
do
{
Task<int> result;
bool success = popupQueue.TryPeek(out result);
if (!success) break;
await result;
}
while (true);
Then I read about a AsyncProducerConsumerCollection but I'm not sure if that is the most simple solution.
How can I ensure thread safety in a simple way? Thank you.
This code is not thread-safe. Whatever else may be true, InternalFireQueuedAsync is racy if called by multiple threads. If one thread is running the while loop, it may reach a point at which it is empty.
Thread safe means that you have to isolate any shared data. Here your shared data is the pointer to the queue.So , in general , any time you have operations on the queue you need to protect queue and prevent multiple threads reach your queue at the same time. One good way is to implement Condition Variables.
The async and await keywords don't cause additional threads to be created. Async methods don't require multithreading because an async method doesn't run on its own thread. The method runs on the current synchronization context and uses time on the thread only when the method is active.
Task does not guarantee parallel execution. Task does not belong to a Thread or anything like that. They are two separate concepts and should be treated as such. Task represents some work that needs to be done.
To simply add thread-safety you should use a ConcurrentQueue
. It's a thread-safe implementation of a queue and you can use it just the same way you would use a queue without worrying about concurrency.
However if you want a queue you can await
asynchronously (which means that you're not busy-waiting or blocking a thread while waiting) you should use TPL Dataflow's BufferBlock
which is very similar to AsyncProducerConsumerCollection
only already implemented for you by MS:
var buffer = new BufferBlock<int>();
await buffer.SendAsync(3); // Instead of enqueue.
int item = await buffer.ReceiveAsync(); // instead of dequeue.
ConcurrentQueue
is fine for thread-safeness, but wasteful in high levels of contention. BufferBlock
is not only thread-safe it also gives you asynchronous coordination (usually between a consumer and producer).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With