Volatile DateTime
Asked Answered
T

5

13

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

Tillman answered 28/1, 2011 at 16:13 Comment(7)
Not sure if what you're trying to do makes sense. How do you define latest version?Blacken
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.Gownsman
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.Wainscot
@Eric, question: the DateTime.Now doesn't list a 1/64 of a second. Where is that documented?Ultan
@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.Wainscot
@Eric: Gotcha. I was calculating 1/64 = 0.015 seconds, or 15.6ms. But I didn't see the word "approximate". Thanks.Ultan
Somewhat related: Thread safe DateTime update using Interlocked.*Lactic
G
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
Gownsman answered 28/1, 2011 at 16:15 Comment(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.)Undercroft
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.Disc
@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.Gownsman
You are setting the value atomically, but nothing guarantees that the Touched property will read it atomically.Pattiepattin
I think there's missing a new keyword in front of DateTime(Interlocked.Read(ref m_Touched));Clarisaclarise
B
5

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.

Blacken answered 28/1, 2011 at 16:21 Comment(2)
this is a x64 application for x64 machines. does it matter? Good point.Tillman
@Tillman 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://mcmap.net/q/95403/-what-operations-are-atomic-in-cKauffmann
D
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.

Dysphemism answered 28/1, 2011 at 16:15 Comment(0)
L
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;
Lactic answered 3/4, 2022 at 17:43 Comment(0)
H
0

Would this work?

public class VolatileDateTime
{
    private readonly DateTime _datetime;
    public VolatileDateTime(DateTime datetime)
    {
        _datetime = datetime;
    }

    public DateTime Value { get => _datetime; }
}

...

volatile VolatileDateTime vDateTime = new VolatileDateTime(DateTime.Now)

...

if (vDateTime.Value > somedatetime)
    dosomething();
Huggins answered 20/6 at 23:22 Comment(4)
Thank you for contributing to the Stack Overflow community. This may be a correct answer, but it’d be really useful to provide additional explanation of your code so developers can understand your reasoning. This is especially useful for new developers who aren’t as familiar with the syntax or struggling to understand the concepts. Would you kindly edit your answer to include additional details for the benefit of the community?Keeler
@Theodore can you be more specific please, in which case it wouldn't? Replacing vDateTime instance is atomic and the compiler/cpu should not optimize for caching the .Value. I agree this is not an efficient approach but why is it not functional?Huggins
Alex ahh, sorry, I misread your code. You are right, it's functional and not the most efficient as you said.Lactic
"Would this work?" -- Btw asking questions as part of the answer is frowned upon here. You have to be assertive. You should explain why it does work, and not ask others to do the explanation.Lactic

© 2022 - 2024 — McMap. All rights reserved.