1

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?

Community
  • 1
  • 1
V.B.
  • 6,236
  • 1
  • 33
  • 56
  • 2
    Have you considered using a `Lazy`? – Dirk Aug 20 '15 at 12:27
  • How `Lazy` would help here if another thread uses `Interlocked.Exchange` to set the target to `null`? – V.B. Aug 20 '15 at 12:33
  • As far as I understand it, you want to have only one instance of some class. *... atomically check for null and allocate a new object only if the target location is null?* That's what `Lazy` does. – Dirk Aug 20 '15 at 12:43
  • No, I want to have only one instance *at a point of time*. Several million times per second. It is like producer-consumer, but producer never produces a value until consumer consumes the single one. – V.B. Aug 20 '15 at 12:46

1 Answers1

4

Before performing the Interlocked operation and the allocation see if the target location is not null already. If yes, there is no need to try and initialize it.

if (Volatile.Read(ref target) == null) InitAtomically();

usr
  • 168,620
  • 35
  • 240
  • 369