-2

I have some flags values in an C# windows service which I want to temporarily be able to change but that will automatically revert to some default value after a period of time. The bare bones of the class look something like this

public class Revertable<T>
{
    public readonly T DefaultValue;
    public T Value { get; protected set; }

    public async Task SetValue(T newValue, TimeSpan resetDelay)
    {
        Value = newValue;
        await Task.Delay(resetDelay);
        Value = DefaultValue;
    }

    public void ResetValue()
    {
        Value = DefaultValue;
    }
}

This is all well and good, but if someone calls SetValue again, I'll now have two interrupts scheduled which could quite possibly step on eachothers toes. One thought was to simply reject any request which tries to call SetValue if !Value.Equals(DefaultValue). That too feels clunky, but sort of works. But one way or another, I need the ability for someone to decide to un-set the value at any time.

I've been looking into the CancellationTokenSource/CancellationToken constructs, but I'm fumbling with how to actually set this all up. It seems like I'd need to add a CancellationTokenSource member to my class (which ResetValue() or others could make use of), and provide that to the Task.Delay() call. If someone calls ResetValue(), the token source initiates a cancel and we move on. So modified, the innards of the class would look something like this:

...
private CancellationTokenSource TokenSource;
public async Task SetValue(T newValue, TimeSpan resetDelay)
{
    if (!Value.Equals(DefaultValue))
        return;

    Value = newValue;
    using (CancellationTokenSource src = new CancellationTokenSource())
    {
        TokenSource = src;
        await Task.Delay(resetDelay, src.Token);
    }
    Value = DefaultValue;
}

public void ResetValue()
{
    if (TokenSource != null && TokenSource.Token.CanBeCanceled)
    {
        TokenSource.Cancel();
        TokenSource.Dispose();
    }
    Value = DefaultValue;
}

I'm somewhat new to async programming, and I don't want to completely botch doing something which seems like it should be relatively straight forward (or leave a bunch of un-disposed token sources floating around). Does this seem like a valid way to go about this? Or can someone suggest a better way?

Isma
  • 14,604
  • 5
  • 37
  • 51
Xedni
  • 3,662
  • 2
  • 16
  • 27
  • Does it work? You tell *us* what problems you're having with your attempted solution. – Servy Oct 16 '17 at 21:41
  • 1
    I would not use async/await for this, I would use a `MemoryCache` and have a rolling window that expires after the delay, any cache misses just return the default value. – Scott Chamberlain Oct 16 '17 at 21:41
  • @Servy it seems to, yes (I had to remove the `using` statement, as it was perpetually in a disposed state). I'm more concerned if this is a good approach, or use of `async` programming, or if there are gotchas I'm not considering. I've been waffling over this for a few days now. – Xedni Oct 16 '17 at 21:59
  • @ScottChamberlain interesting idea. I'm actually quite familiar with MemoryCaches so that's actually an appealing alternative. I'll have to explore that. – Xedni Oct 16 '17 at 21:59

1 Answers1

3

You don't need a thread or a task at all. Just use a timestamp and protect your field with a (non-auto) property.

public class Revertable<T>
{
    public readonly T DefaultValue;
    public readonly TimeSpan ResetDelay;

    private T _currentValue;
    private DateTimeOffset _resetTime;

    public Revertable(T defaultValue, TimeSpan resetDelay)
    {
        this.DefaultValue = defaultValue;
        this.ResetDelay = resetDelay;
        _currentValue = defaultValue;
        _resetTime = DateTimeOffset.MinValue;
    }

    public T Value 
    { 
        get
        {
            return (System.DateTimeOffset.Now > _resetTime)
                ? DefaultValue 
                : _currentValue;
        }
        set
        {
            _currentValue = value;
            _resetTime = System.DateTimeOffset.Now.Add(this.ResetDelay); 
        }
    }
}

public static void Main()
{
    var r = new Revertable<string>("Default value", new TimeSpan(0,0,1));
    Console.WriteLine("Value is now {0}", r.Value);
    r.Value = "Changed";
    for (int i = 0; i<30; i++)
    {
        System.Threading.Thread.Sleep(100);
        Console.WriteLine("Value is now {0}", r.Value);
    }
}

Output:

Value is now Default value
Value is now Changed
Value is now Changed
Value is now Changed
Value is now Changed
Value is now Changed
Value is now Changed
Value is now Changed
Value is now Changed
Value is now Changed
Value is now Default value
Value is now Default value
Value is now Default value
Value is now Default value
Value is now Default value

Full code available on DotNetFiddle.

John Wu
  • 50,556
  • 8
  • 44
  • 80
  • 2
    System.DateTime.UtcNow is a little lighter weight vs System.DateTimeOffset.Now and can be used to do the same calculation. – Scott Chamberlain Oct 17 '17 at 01:02
  • 2
    I suppose. I'm just trying to form good habits, since the dev team [has said](https://blogs.msdn.microsoft.com/bclteam/2007/06/14/datetimeoffset-a-new-datetime-structure-in-net-3-5-justin-van-patten/) "DateTimeOffset is the new preferred type to use for the most common date time scenarios." If you're really worried about performance, maybe use [Environment.TickCount](https://msdn.microsoft.com/en-us/library/system.environment.tickcount(v=vs.110).aspx) instead. – John Wu Oct 17 '17 at 01:14