12 Stimmen

Volatile DateTime

Da DateTime nicht als volatile deklariert werden kann, ist das richtig?

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

Diese Eigenschaft könnte von verschiedenen Threads aus zugegriffen werden, daher möchte ich sicherstellen, dass sie immer die neueste Version erhalten, ohne Verwendung von Sperren (lock).

BEARBEITEN:

  • Ich habe eine Sammlung von schwer zu erstellenden Elementen, von denen jedes eine DateTime-Eigenschaft namens CreationTime hat, die angibt, wann dieses Element erstellt wurde. Es wird auf DateTime.UtcNow initialisiert.
  • Jedes Mal, wenn ein Element abgerufen wird, wird diese Eigenschaft auf DateTime.UtcNow aktualisiert.
  • Es gibt einen Thread, der regelmäßig in einem Thread-Timer ausgeführt wird und überprüft, ob if (DateTime.UtcNow + 1 Stunde) > Element.CreationTime ist, falls es wahr ist, löscht es das Element.

Ich möchte sicherstellen, dass wenn der "Löschthread" in die Sammlung kommt, alle Elemente ihren neuesten "letzten Zugriff" DateTime haben, damit ich vermeiden kann, das Element erneut zu erstellen, nur weil ein Cache den Wert für ein paar Millisekunden gehalten hat. :D

0 Stimmen

Nicht sicher, ob das, was du versuchst zu tun, einen Sinn ergibt. Wie definierst du die neueste Version?

1 Stimmen

Angesichts dessen, dass Sie einen Cache implementieren, der ungenutzte Objekte entfernt, die älter als ein bestimmter Zeitraum sind, denke ich, dass die InterlockedExchange-Lösung hier der richtige Weg ist.

2 Stimmen

Sie sind sich bewusst, dass DateTime.Now nur genau bis zu 1/64 einer Sekunde ist, oder? Wenn Sie sich über Ungenauigkeiten aufgrund von Caches, die um Millisekunden verzögert sind, sorgen, dann haben Sie bereits verloren; die Präzision der Daten, die Sie versuchen korrekt zu halten, ist zu jeder Zeit weit weniger als 1/1000 einer Sekunde.

18voto

Moo-Juice Punkte 37380

Genau.

Aber Sie haben eine andere Option. Speichern Sie die Zeit als Int64 Tick-Zähler und verwenden Sie InterlockedExchange zum Setzen. Threads können dann ihre eigene DateTime mithilfe des Int64 Konstruktors erstellen, was Ihnen keine Konflikte und keine Sperren gibt.

BEARBEITEN:

Nachdem Sie weitere Informationen zur Verfügung gestellt haben, ist es jetzt einfacher, ein Beispiel zu geben.

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

5 Stimmen

Besser ist es, ToBinary/FromBinary anstelle des reinen Speicherns der Rohticks zu verwenden, damit auch DateTimeKind erhalten bleibt. (Obwohl ich behaupten würde, dass der OP wahrscheinlich nur ein lock und ein normales DateTime verwenden sollte, anstatt zu versuchen, zu clever zu sein.)

2 Stimmen

Obwohl möglich, klingen beide Vorschläge so, als ob es viele überflüssige Umwandlungen geben könnte. Das hängt natürlich davon ab, wie viele Verbraucher diesen DateTime-Wert haben.

2 Stimmen

@Sensai76, vielleicht, aber ich bezweifle, dass die Umwandlung zu einem lästigen Overhead wird (obwohl es schwer zu sagen ist, ohne zu wissen, wie dies von den anderen Threads verwendet wird), und darüber hinaus wird es weniger Overhead sein als eine Art von Sperre.

3voto

Oded Punkte 475566

Dies ist nicht möglich - Sie müssen lock oder die Monitor-Klasse verwenden, um den Zugriff auf das Feld zu synchronisieren.

Das liegt daran, dass DateTime ein Werttyp - eine Struktur - ist.

Von MSDN - volatile (C# Referenz):

Das volatile-Schlüsselwort kann auf Felder dieser Typen angewendet werden:

  • Referenztypen.
  • Zeigertypen (in einem unsicheren Kontext). Beachten Sie, dass obwohl der Zeiger selbst volatil sein kann, das Objekt, auf das er zeigt, nicht. Mit anderen Worten, Sie können nicht einen "Zeiger auf volatil" deklarieren.
  • Typen wie sbyte, byte, short, ushort, int, uint, char, float und bool.
  • Ein Aufzählungstyp mit einem der folgenden Basistypen: byte, sbyte, short, ushort, int oder uint.
  • Generische Typparameter, von denen bekannt ist, dass sie Referenztypen sind.
  • IntPtr und UIntPtr.

Wie andere bereits erwähnt haben, können Sie Ticks verwenden, um die Zeit zu verfolgen.

3voto

CodesInChaos Punkte 103089

Ihr Code ist nicht threadsicher, da die Zuweisung von DateTime nicht garantiert atomar ist. Im Allgemeinen sind Zuweisungen von Ganzzahlen bis zu 32 Bits atomar, aber 64 Bits müssen nicht atomar sein.

Sie können wahrscheinlich Interlocked.Exchange mit den Ticks des DateTime verwenden, da dies atomar einen Int64 speichern kann.

Aber wenn Sie zu Ticks wechseln, müssen Sie wissen, dass nur 62 Bits für die Ticks verwendet werden und 2 Bits für die Art. So verlieren Sie die Art nicht.

Selbst wenn Sie den Getter und Setter atomar und threadsicher machen, bin ich mir nicht sicher, ob das ausreicht. Da die Zeit zwischen dem Zeitpunkt, an dem Ihr Getter zurückgibt und dem Zeitpunkt, an dem Sie tatsächlich mit der erhaltenen Zeit arbeiten, ändern kann. So kann Ihre Zeit immer veraltet sein.

1 Stimmen

Dies ist eine x64-Anwendung für x64-Maschinen. Ist das wichtig? Guter Punkt.

2 Stimmen

@vtortola gemäß diesem Artikel können 64-Bit-Zuweisungen atomar sein: Die CLI garantiert, dass Lese- und Schreibvorgänge von Variablen von Werttypen, die die Größe (oder kleiner) der natürlichen Zeigergröße des Prozessors haben, atomar sind; wenn Sie C#-Code auf einem 64-Bit-Betriebssystem in einer 64-Bit-Version der CLR ausführen, sind Lese- und Schreibvorgänge von 64-Bit-Doubles und langen ganzen Zahlen ebenfalls atomar garantiert. Die C#-Sprache garantiert dies nicht, aber die Laufzeitspezifikation tut es. stackoverflow.com/a/11745471/67824

1voto

Theodor Zoulias Punkte 22972

Das .NET Core enthält die Unsafe.As Methode, die es ermöglicht, ein volatiles Lesen/Schreiben an einer DateTime Variablen durchzuführen, wie folgt:

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

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

Verwendung:

DateTime latest = VolatileRead(ref _dateTimeField);

VolatileWrite(ref _dateTimeField, newDateTime);

Dies ist ein Hack, da es davon abhängt, dass der Typ DateTime für immer von einem ulong Feld unterstützt wird. Verwenden Sie Ihr eigenes Urteilsvermögen, ob es angemessen/sicher/klug ist, es in jedem Fall zu verwenden.

Aus dem Quellcode der DateTime Struktur:

private readonly ulong _dateData;

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X