Recently I am rewriting an old App using WinRT, I need to display a clock, but I find there is no Timer control.

It looks like I have to implement the Timer myself. I don't use Thread.Sleep because it will block UI thread. I prefer using async await over it. To replace Thread.Sleep, I use Task.Delay

while (true)
{
    // work
    await Task.Delay(ms);
}

To increase reusability, we need to encapsulate further.

The first is the loop condition and the number of milliseconds, which can be controlled, so add attributes:

public int Interval { get; set; }
public bool IsEnabled { get; set; }

This allows you to:

while (IsEnabled)
{
    // 要做的操作
    await Task.Delay(Interval);
}

Further decoupling is to leave the operation part to the user to implement, the glorious C# language provides an Action delegate to extract this part of the code, so it is necessary to define the properties of the type of an Action delegate, and let the user decide the behavior by himself.

public Action<int> DoAction { get; set; }

Because the user may want to read the value of interval, the action parameter is int interval. Now you can do this:

while (IsEnabled)
{
    DoAction(Interval);
    await Task.Delay(Interval);
}

In recent years, the promise and futures programming styles have become popular. By returning this, you can type "." all the way down when you write code. So the place to assign a value to a property can be designed like this:

public AsyncTimer SetInterval(int ms)
{
    if (ms <= 0)
    {
        this.Interval = ms;
    }
    else
    {
        throw new ArgumentOutOfRangeException("ms", "interval must be larger than zero.");
    }
    return this;
}

public AsyncTimer WhenTick(Action<int> action)
{
    this.DoAction = action;
    return this;
}

既然用户要使用Timer肯定得有一个interval,所以要用带参构造去勾引用户完成interval的赋值,同时也允许不带参构造函数。

public AsyncTimer(int? ms = null)
{
    if (null != ms)
    {
        if (ms <= 0)
        {
            throw new ArgumentOutOfRangeException("ms", "interval must be larger than zero.");
        }
        Interval = ms.Value;
    }
}

最后增加一对start/stop方法,也可以再奖励一个toggle方法,就可以完成装逼了,最终封装成这样:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DateDiffer.WinRT
{
    public class AsyncTimer
    {
        public int Interval { get; set; }

        public bool IsEnabled { get; set; }

        public Action<int> DoAction { get; set; }

        public AsyncTimer(int? ms = null)
        {
            if (null != ms)
            {
                if (ms <= 0)
                {
                    throw new ArgumentOutOfRangeException("ms", "interval must be larger than zero.");
                }
                Interval = ms.Value;
            }
        }

        public AsyncTimer SetInterval(int ms)
        {
            if (ms <= 0)
            {
                this.Interval = ms;
            }
            else
            {
                throw new ArgumentOutOfRangeException("ms", "interval must be larger than zero.");
            }
            return this;
        }

        public void Stop()
        {
            IsEnabled = false;
        }

        public AsyncTimer WhenTick(Action<int> action)
        {
            this.DoAction = action;
            return this;
        }

        public async Task StartAsync()
        {
            if (Interval <= 0)
            {
                throw new InvalidOperationException("interval must be set.");
            }

            if (DoAction == null)
            {
                throw new InvalidOperationException("action must be set.");
            }

            IsEnabled = true;

            while (IsEnabled)
            {
                DoAction(Interval);
                await Task.Delay(Interval);
            }
        }

        public void Toggle()
        {
            IsEnabled = !IsEnabled;
        }
    }
}

然后调用的地方就是这样:

TimerTask = new AsyncTimer(1000).WhenTick(i => OnOneSecondPassed()).StartAsync();
...
protected virtual void OnOneSecondPassed()
{
    CurrentTime = DateTime.Now;
}