I'm using a ConcurrentQueue<Result>
to store results of a certain activity that occurs in my system (ASP.NET Core 6). It works well.
But now I have a new requirement: I need a Result.Id
property - a long
value, similar to a sequential database primary key. When I enqueue an item, it must get the next value in the sequence, i.e. queue.Count + 1
.
When I enqueue items, I don't use locks because ConcurrentQueue
does that for me. But now that need Id
, I must:
Enqueue()
Id
or:
Enqueue()
Id
(but it will be in an inconsistent state until then)Can I do this without locking the Enqueue()
method? Or maybe there's a better way? (Though I prefer to continue using ConcurrentQueue
, if possible, as it already works well for what I have.)
Instead of assigning the Id when adding the items, you can assign it after you have finished adding items to the queue. You can use a Select
with index, e.g.:
public class Item {
public int Id { get; set; }
public int Value { get; set; }
}
public static void Main()
{
var q = new ConcurrentQueue<Item>();
Parallel.For(0, 10, i => q.Enqueue(new Item() { Value = i + 100 }));
var withIds = q.Select((x, i) => {
x.Id = i;
return x;
});
Console.WriteLine(string.Join(", ", withIds.Select(x => string.Format("{0} -> {1}", x.Id, x.Value))));
}
This leads to the following output:
0 -> 100, 1 -> 102, 2 -> 103, 3 -> 105, 4 -> 101, 5 -> 106, 6 -> 107, 7 -> 104, 8 -> 108, 9 -> 109
Depending on the size of the queue and the number of times you iterate it, you can also add a ToArray
after the Select
in order to avoid assigning the Ids multiple times.
Important to note that this only works if you can clearly separate the phase of adding items to the queue and processing the queue.