The Curious Case of .NET ConcurrentDictionary and Closures :
by:
blow post content copied from Khalid Abuhakmeh
click here to view original post
I was recently looking at the Duende Software codebase, and I kept seeing the same suggestion offered by the IDE tooling whenever I encountered a ConcurrentDictionary
: “Closure can be eliminated: method has overload to avoid closure creation.”
While the suggestion appears in the tooling, there isn’t a quick fix action to apply the change. It left me scratching my head because there wasn’t an immediately obvious solution.
This post will define closures and explain their problems. We’ll also explain how to change your usage of ConcurrentDictionary
to avoid closures altogether.
What Are Closures?
If you’ve ever worked with an Action
, Func
, delegate
, or LINQ, then you’ve likely encountered a closure. A closure is a language mechanism that allows you to treat a function with free variables as if it were an object instance you may pass, invoke, or use in another context from when you first created it. Justin Etheredge has a great article explaining closures in-depth, but it’s when you use a lambda with a state outside the current scope of the lambda.
Let’s create a closure by capturing a variable in a straightforward example.
void SayHello(string name)
{
var hello = () =>
{
// name is captured causing an allocation
// and potential concurrency issues
Console.WriteLine($"Hello {name}");
};
hello();
}
In the code above, the compiler needs to capture the name
parameter to ensure all future calls to our hello
lambda can execute. Capture can cause issues that may be difficult to predict until you execute your code.
- Additional allocations are needed to capture a value and can have resource utilization implications.
- A value captured from outside the closure scope may be alterable if it is a reference type. Unintended state change can lead to unpredictable behavior.
- Long-lived references may lead to memory leaks in the long run.
To avoid capture, ensure all lambdas pass state required as arguments.
void SayHello(string name)
{
var hello = (string n) =>
{
Console.WriteLine($"Hello {n}");
};
hello(name);
}
Now that we understand the basics, let’s examine the ConcurrentDictionary
suggestion and see how we might fix it.
ConcurrentDictionary.GetOrAdd and Closure Creation
Let’s write a straightforward use of the GetOrAdd
method on ConcurrentDictionary
and see what the issue might be.
using System.Collections.Concurrent;
ConcurrentDictionary<string, Item> concurrentDictionary
= new();
var key = "khalid";
var value = "awesome";
var result = concurrentDictionary.GetOrAdd(key, (k) => {
Console.WriteLine($"Building {k}");
return new Item(value, DateTime.Now);
});
Console.WriteLine(result);
Looking at the code, what variable do you think is creating the unnecessary closure?
If you guessed value
, then you would be correct!
How do we fix the closure since our tooling now suggests that there is a solution to this issue?
Closure can be eliminated: method has overload
to avoid closure creation.
Well, let’s refactor and see how our code changes. I’ll add parameter prefixes to make clear what is happening.
using System.Collections.Concurrent;
ConcurrentDictionary<string, Item> concurrentDictionary = new();
var key = "khalid";
var value = "awesome";
var result = concurrentDictionary.GetOrAdd(
key: key,
valueFactory: (k, arg) =>
{
Console.WriteLine($"Building {k}");
return new Item(arg, DateTime.Now);
},
factoryArgument: value);
Console.WriteLine(result);
record Item(string Value, DateTime Time);
So there is an overload on ConcurrentDictionary.GetOrAdd
that takes three parameters:
- The key to our value.
- The lambda function responsible for creating our value when we do not find it.
- A singular factory argument. You must wrap multiple arguments in a container class.
Using this overload, we now avoid closures, reduce allocations, and avoid potentially nasty concurrency or memory leak issues.
If you’re using ConcurrentDictionary
check to see if you’re using GetOrAdd
and see if you’re using the more efficient overload. Thanks for reading and sharing my posts with friends and colleagues. Cheers.
February 18, 2025 at 05:30AM
Click here for more details...
=============================
The original post is available in Khalid Abuhakmeh by
this post has been published as it is through automation. Automation script brings all the top bloggers post under a single umbrella.
The purpose of this blog, Follow the top Salesforce bloggers and collect all blogs in a single place through automation.
============================

Post a Comment