Here is a way to keep track of the expiration status of an object passively, without using timers. The last time that each object was used is stored in a private double
field, and this field is updated every time the object is used. In case the object has not been used for a long time, the field will take the value double.MaxValue
which means "expired", and will keep this value forever. The GetExpired
method below handles the complexity of comparing and updating the field atomically and with thread-safety:
public static bool GetExpired(ref double lastUsed, TimeSpan slidingExpiration,
bool touch)
{
// Magic values, 0: Not initialized, double.MaxValue: Expired
double previous = Volatile.Read(ref lastUsed);
if (previous == double.MaxValue) return true;
// Get current timestamp in seconds
double now = (double)Stopwatch.GetTimestamp() / Stopwatch.Frequency;
if (previous == 0D || now - previous < slidingExpiration.TotalSeconds)
{
// Not expired (unless preempted)
if (!touch) return false;
var original = Interlocked.CompareExchange(ref lastUsed, now, previous);
return original == double.MaxValue;
// In any other case that original != previous we've lost the race to update
// the field, but its value should be very close to 'now'. So not expired.
}
else
{
// Expired (unless preempted)
var original = Interlocked.CompareExchange(ref lastUsed, double.MaxValue,
previous);
return original == double.MaxValue || original == previous;
}
}
Usage example:
public class MyComObject
{
private readonly TimeSpan _slidingExpiration = TimeSpan.FromSeconds(60);
private double _lastUsed;
public MyComObject() // Constructor
{
GetExpired(ref _lastUsed, default, touch: true); // Start expiration "timer"
}
public bool IsExpired => GetExpired(ref _lastUsed, _slidingExpiration, touch: false);
public bool TryDoSomething()
{
if (GetExpired(ref _lastUsed, _slidingExpiration, touch: true)) return false;
//...
return true; // The job was done
}
}
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…