PROWAREtech

articles » current » asp-net-core » concurrentdictionary-explained

ASP.NET Core: ConcurrentDictionary - What is It and How to Use it?

What a ConcurrentDictionary is plus example webapi/endpoint usage in C#.

See also .NET ConcurrentBag class.

A ConcurrentDictionary in C# is a thread-safe collection that stores key-value pairs, designed for scenarios where multiple threads need to simultaneously add, remove, or modify items. Unlike a regular Dictionary, it provides atomic operations that are safe for concurrent access without explicit locking, making it ideal for producer-consumer patterns or caching scenarios where one needs to ensure data consistency across threads.

A ConcurrentBag, on the other hand, is an unordered collection of objects that allows duplicate values and is optimized for scenarios where the same thread is both adding and removing items. While both collections are thread-safe, ConcurrentBag doesn't maintain key-value relationships and excels in situations where one doesn't need to track specific items but rather just need a thread-safe container for objects, like a pool of resources.

The main difference lies in their use cases: ConcurrentDictionary is best when one needs to maintain unique keys and look up specific values quickly, while ConcurrentBag is more suitable for scenarios where one's just collecting items and don't care about their order or uniqueness. ConcurrentDictionary provides O(1) lookup performance, while ConcurrentBag is optimized for scenarios where the same thread that added an item is likely to remove it.

Example Code

Here's a more complete real-world example showing a thread-safe request rate limiter:


using System.Collections.Concurrent;

public class RateLimiter
{
	private readonly ConcurrentDictionary<string, Queue<DateTime>> _requestTimes = new ConcurrentDictionary<string, Queue<DateTime>>();
	private readonly int _maxRequests;
	private readonly TimeSpan _timeWindow;

	public RateLimiter(int maxRequests, TimeSpan timeWindow)
	{
		_maxRequests = maxRequests;
		_timeWindow = timeWindow;
	}

	public bool CanMakeRequest(string clientId)
	{
		return _requestTimes.AddOrUpdate(
			clientId,
			// If client doesn't exist, create new queue with one timestamp
			_ => new Queue<DateTime>(new[] { DateTime.UtcNow }),
			// If client exists, update their request times
			(_, queue) =>
			{
				// Remove old timestamps outside the window
				while (queue.Count > 0 &&
					   DateTime.UtcNow - queue.Peek() > _timeWindow)
				{
					queue.Dequeue();
				}

				// If under limit, add new timestamp
				if (queue.Count < _maxRequests)
				{
					queue.Enqueue(DateTime.UtcNow);
					return queue;
				}

				// Over limit, return unchanged queue
				return queue;
			}
		).Count <= _maxRequests;
	}
}

// Usage:
var rateLimiter = new RateLimiter(maxRequests: 100, timeWindow: TimeSpan.FromMinutes(1));

// In the API endpoint
if (rateLimiter.CanMakeRequest(userId))
{
	// Process the request
}
else
{
	// Return 429 Too Many Requests
}

PROWAREtech

Hello there! How can I help you today?
Ask any question

PROWAREtech

This site uses cookies. Cookies are simple text files stored on the user's computer. They are used for adding features and security to this site. Read the privacy policy.
ACCEPT REJECT