I have a multi-threaded application that implements async
methods. The application communicates over RabbitMq and at times needs to do a costly operation (BasicConsume
on an IModel
). I have a ConcurrentDictionary where I keep IModel
(key) and it's IBasicConsumer
(value) in order to avoid additional round-trips to the message broker.
On start-up, multiple executions reach the same section of the code where it looks if the consumer (value) exists. In the first ~40 cases the answer is no, but I don't want to do 40 BasicConsume
, only one or perhaps two depending on the IModel
(key) provided.
Using a lock
statement introduces a performance hit that is not acceptable.
Currently, I'm solving the problem as follows
var consumerTcs = new TaskCompletionSouce<IBasicConsumer>();
if (_consumerDictionary.TryAdd(channel, consumerTcs))
{
var newConsumer = new EventingBasicConsumer(channel);
newConsumer.Model.BasicConsume(queue, false, newConsumer)
consumerTcs.TrySetResult(newConsumer);
return consumerTcs.Task;
}
else
{
return _consumerDictionary[channel].Task;
}
That is
- Use a
TaskCompletionSource
(non-expencive) as the value of the dictionary - Use
TryAdd
, and if successful do the expensive operation - If unsuccessful, assume that the key exists and return the
TaskCompletionSource.Task
However, I still create TaskCompletionSources
that I don't use, which feels a bit ugly to me. Any suggestions to how I might solve this problem without the performance hit of a lock
and without wasting clock cycles on creating objects that is not used?