12

As DateTime cannot be declared as volatile, is this right?

private DateTime _time;
public DateTime Time
{
    get
    {
        Thread.MemoryBarrier();
        return _time;
    }
    set
    {
        _time = value;
        Thread.MemoryBarrier();
    }
}

That property could be accessed from different threads, so I want to ensure they get always the latest version, without use contention (lock).

EDIT:

  • I have a collection of hard-to-create items, each one has a DateTime property named CreationTime, indicating when this item was created. It's initialized to DateTime.UtcNow.
  • Every time a item is accessed, that property is updated to DateTime.UtcNow.
  • There is a thread, that executes in timely fashion in a threaded timer that checks if (DateTime.UtcNow + 1 hour) > item.CreationTime, if true it deletes the item.

I want to ensure that when the "deletion thread" comes into the collection, all the items have their latest "last access" DateTime on it, so I can avoid create the item again just because a cache held the value for a couple of milliseconds. :D

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
vtortola
  • 34,709
  • 29
  • 161
  • 263
  • Not sure if what you're trying to do makes sense. How do you define latest version? – CodesInChaos Jan 28 '11 at 16:26
  • 1
    Given that you're implementing a cache which removes un-used objects older than a particular time period, I think the `InterlockedExchange` solution is the way to go here. – Moo-Juice Jan 28 '11 at 16:44
  • 2
    You are aware that DateTime.Now is only accurate to 1/64 of a second, right? If you're worried about inaccuracy due to caches delaying by milliseconds then you've already lost; the precision of the data you are trying to keep accurate is far, far less than 1/1000 of a second at all times. – Eric Lippert Jan 28 '11 at 16:58
  • 1
    @Eric, question: the [DateTime.Now](http://msdn.microsoft.com/en-us/library/system.datetime.now.aspx) doesn't list a 1/64 of a second. Where is that documented? –  Jan 28 '11 at 18:32
  • 1
    @yodaj007: It does too. It says that on operating systems NT3.5 and greater the precision is approximately 10 milliseconds, or 1/100th of a second. "Approximately" means "not exactly"; the operating system is allowed to decide what the appropriate precision is. It's typically close to the thread quantum, for obvious reasons. On my machine it's precise to 1/64th of a second; your machine might be better or worse. – Eric Lippert Jan 28 '11 at 20:51
  • 1
    @Eric: Gotcha. I was calculating 1/64 = 0.015 seconds, or 15.6ms. But I didn't see the word "approximate". Thanks. –  Jan 28 '11 at 21:07
  • Somewhat related: [Thread safe DateTime update using Interlocked.*](https://stackoverflow.com/questions/1531668/thread-safe-datetime-update-using-interlocked) – Theodor Zoulias Jan 12 '23 at 16:40

4 Answers4

18

Precisely.

But, you have another option. Store the time as an Int64 tick count, and use InterlockedExchange to set. Threads can then construct their own DateTime using The Int64 constructor, giving you no contention and no locks.

EDIT:

Given that you've provided more information, it's easier now to provide an example.

public class Cache
{
    class CacheEntry
    {
        private Int64 m_Touched;

        public CacheEntry()
        {
            Touch();
        }

        public void Touch() 
        {
            System.Threading.Interlocked.Exchange(ref m_Touched, DateTime.Now.Ticks);
        }

        public DateTime Touched
        {
            get
            {
                return new DateTime(Interlocked.Read(ref m_Touched));
            }
        }
    } // eo class CacheEntry
} // eo class Cache
Moo-Juice
  • 38,257
  • 10
  • 78
  • 128
  • 5
    Better to use `ToBinary`/`FromBinary` rather than just storing the raw ticks, so that the `DateTimeKind` is preserved as well. (Although I'd argue that the OP should probably just use a `lock` and a plain `DateTime` rather than trying to be over-clever.) – LukeH Jan 28 '11 at 16:19
  • 2
    Although possible, both of these suggestions sound like there might be a lot of redundant conversions involved. That, of course, depends on how many consumers that DateTime value has. – Jens H Jan 28 '11 at 16:21
  • 2
    @Sensai76, perhaps but I doubt the conversion is going to be a troublesome overhead (though it's hard to say without knowing how this is used by the other threads), and that said, it will be less of an overhead than some kind of lock. – Moo-Juice Jan 28 '11 at 16:23
  • 1
    You are setting the value atomically, but nothing guarantees that the `Touched` property will *read* it atomically. – vgru Apr 23 '14 at 10:39
  • I think there's missing a new keyword in front of DateTime(Interlocked.Read(ref m_Touched)); – Kenci Aug 03 '16 at 11:53
3

This is not possible - you will need to use lock or the Monitor class to synchronize access to the field.

This is because DateTime is a value type - a structure.

From MSDN - volatile (C# Reference):

The volatile keyword can be applied to fields of these types:

  • Reference types.
  • Pointer types (in an unsafe context). Note that although the pointer itself can be volatile, the object that it points to cannot. In other words, you cannot declare a "pointer to volatile."
  • Types such as sbyte, byte, short, ushort, int, uint, char, float, and bool.
  • An enum type with one of the following base types: byte, sbyte, short, ushort, int, or uint.
  • Generic type parameters known to be reference types.
  • IntPtr and UIntPtr.

As others have mentioned, you can use Ticks to track time.

concision
  • 6,029
  • 11
  • 29
Oded
  • 489,969
  • 99
  • 883
  • 1,009
3

Your code isn't thread-safe since the assignment of DateTime isn't guaranteed to be atomic. In general assignments of integers up to 32bits are atomic, but 64 needn't be atomic.

You probably can use Interlocked.Exchange with the ticks of the DateTime since that can atomically store an Int64.

But if you switch to ticks you need to know that only 62 bits are used for the ticks, and 2 bits for the kind. So you don't lose the kind.

And even if you make the getter and setter atomic an threadsafe, I'm not sure if that's enough. Since the time can change between the time where your getter returns and the time you actually work with the time you got. So your time can always be outdated.

CodesInChaos
  • 106,488
  • 23
  • 218
  • 262
  • 1
    this is a x64 application for x64 machines. does it matter? Good point. – vtortola Jan 28 '11 at 16:43
  • 2
    @vtortola according to this, 64 bit assignments can be atomic: _The CLI guarantees that reads and writes of variables of value types that are the size (or smaller) of the processor's natural pointer size are atomic; if you are running C# code on a 64 bit operating system in a 64 bit version of the CLR then reads and writes of 64 bit doubles and long integers are also guaranteed to be atomic. The C# language does not guarantee that, but the runtime spec does._ https://stackoverflow.com/a/11745471/67824 – Ohad Schneider Nov 28 '17 at 09:49
1

The .NET Core includes the Unsafe.As method, which allows to perform a volatile read/write on a DateTime variable like this:

public static DateTime VolatileRead(ref DateTime location)
{
    ref ulong unsafeLocation = ref Unsafe.As<DateTime, ulong>(ref location);
    ulong result = Volatile.Read(ref unsafeLocation);
    return Unsafe.As<ulong, DateTime>(ref result);
}

public static void VolatileWrite(ref DateTime location, DateTime value)
{
    ref ulong unsafeLocation = ref Unsafe.As<DateTime, ulong>(ref location);
    ref ulong unsafeValue = ref Unsafe.As<DateTime, ulong>(ref value);
    Volatile.Write(ref unsafeLocation, unsafeValue);
}

Usage:

DateTime latest = VolatileRead(ref _dateTimeField);

VolatileWrite(ref _dateTimeField, newDateTime);

This is a hack, as it depends on the DateTime type being backed by a ulong field forever. Use your own judgement whether it's appropriate/safe/prudent to use it, in a case-by-case basis.

From the source code of the DateTime struct:

private readonly ulong _dateData;
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104