I have just realized that I allocate a lot of objects in Interlocked.CompareExchange
and throw them away to GC, because the value (second argument) is always evaluated, compared to &&
or ||
, which use short-circuit
evaluation .
Is lock the only alternative to atomically check for null and allocate a new object only if the target location is null
?
This test prints "I am created" three times and fails on the last assertion.
internal class TestCompareExchange {
public static TestCompareExchange defalt = new TestCompareExchange();
public static bool allocated = false;
public TestCompareExchange() {
allocated = true;
Console.WriteLine("I am created");
}
}
[Test]
public void CompareExchangeAllocatesValue() {
if (TestCompareExchange.allocated && (new TestCompareExchange()) != null) // the second part after && is not evaluated
{
}
Assert.IsFalse(TestCompareExchange.allocated);
TestCompareExchange target = null;
var original = Interlocked.CompareExchange(ref target, new TestCompareExchange(), (TestCompareExchange)null);
Assert.AreEqual(null, original);
Assert.IsTrue(TestCompareExchange.allocated);
TestCompareExchange.allocated = false;
target = null;
original = Interlocked.CompareExchange(ref target, new TestCompareExchange(), TestCompareExchange.defalt);
Assert.AreEqual(null, original);
Assert.IsFalse(TestCompareExchange.allocated); // no exchange, but objetc is allocated
}
In my real code, I use TaskCompletionSource
instead of the fake object. Does it matter? Is there some pooling for TCS objects that makes allocation and collection irrelevant for them?