C#实现时间轮定时器

本文介绍了如何使用C#实现时间轮定时器,包括任务部分(WheelTask类)和时间轮部分(TimeWheel类)的详细设计。通过.Net6.0框架,创建了一个支持同步和异步执行的任务模型,并利用多级时间轮(年、月、日、时、分、秒、毫秒)进行任务调度。在主程序中,启动新线程运行时间轮更新,确保任务的执行和循环管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

最近了解了时间轮定时器原理和linux内核用c语言实现的时间轮定时器源码之后。按照自己的理解,突发奇想 自己用C#来实现一遍。

正题

时间轮定时器的组成就分两个部分。

        一是任务部分,WheelTask.cs。

        二是时间轮部分,和TimeWheel.cs。

以下代码用的是 .Net6.0 框架的。

首先来看任务部分。

WheelTask类里面存储一些必要的信息:

1.任务id (方便日后移除任务)、

2.延迟时间、

3.时间间隔、

4.循环次数、

5.执行时间、

6.执行函数、

7.已循环次数。

这些没什么好说的,都是字面意思。

然后初始化对象时,执行时间就取当前时间+延迟时间。

            dateTime = DateTime.Now;
            dateTime = dateTime.AddMilliseconds(this.delay);

执行任务的话有两种方式,看自己需要是想同步还是异步执行。

第一种,时间到了之后执行,等待执行完后再算后面循环的时间戳,也就是阻塞线程的。

        public void Run()
        {
            action();
        }

第二种,时间到了之后执行,但是不会等待它执行完就计算后面要循环的时间戳,也就是不阻塞线程的。

        public async Task RunAsync()
        {
            await Task.Run(() =>
            {
                action();
            });
        }

检查循环,每次调用了Run()或者RunAsync()之后执行CheckLoop()方法。

如果还有要循环的次数,那么就刷新dateTime,将其改为当前时间+时间间隔,也就是下一次执行的时间。

        public bool CheckLoop()
        {
            m_hadLoopTimes++;
            if (loopTimes == -1)
            {
                dateTime = DateTime.Now.AddMilliseconds(interval);
                return true;
            }
            else if(loopTimes > 0)
            {
                if (loopTimes > m_hadLoopTimes)
                {
                    dateTime = DateTime.Now.AddMilliseconds(interval);
                    return true;
                }
            }
            return false;
        }

接下来看时间轮部分。

TimeWheel类的代码较多,这里先逐个讲解。

首先,我们知道肯定要有一个while循环来驱动时间轮运转,所以我们定义一个Update方法用来更新时间轮。

        public void Update()
        {
            lock (s_obj)
            {
                DateTime now = DateTime.Now;
                //下面这一块只是为了打印,不用管它
                if (now.Ticks - m_temp >= 10000000)
                {
                    Console.WriteLine("当前时间 " + now);
                    m_temp = now.Ticks;
                }
                //TODO... 一些时间轮调度的逻辑
            }
        }

那么这个Update方法在哪里调用呢,在这里就先插一嘴提提主程序入口的样子。

因为while循环会阻塞线程。所以,在主程序入口里面,我们就开启一个新的线程来while循环,保证不卡主线程,while循环里面就疯狂调用Update方法。

using TimeWheelProject;

internal class Program
{
    public static TimeWheel timeWheel;
    private static Thread thread;

    private static void Main(string[] args)
    {
        timeWheel = new TimeWheel();
        thread = new Thread(TimeWheelProgram);
        thread.Start();
        Console.ReadLine();
    }

    public static void TimeWheelProgram()
    {
        while (true)
        {
            timeWheel.Update();
        }
    }
}

在Update()方法里面,要处理的最核心逻辑就是关于时间轮的调度,也就是将任务WheelTask不断推到最终的执行轮(毫秒轮)上,再执行任务。

那么,关于轮的定义,如下图所示:

 

 

上面举例看到,时、分、秒三个轮分别按照平时的钟表刻度来进行分割。

 在代码中,不止时、分、秒这3个轮,还有年、月、日、毫秒轮。

每个轮盘用字典Dictionary存储:

key值是分割数(或者叫槽位数),月、日、时、分、秒轮都是按正常时间刻度分,只有年轮、毫秒轮按自定义分。

value是WheelTask类型的双链表LinkedList

        /// <summary>年轮 </summary>
        private Dictionary<int, LinkedList<WheelTask>> wheelYear;
        /// <summary>月轮 </summary>
        private Dictionary<int, LinkedList<WheelTask>> wheelMonth;
        /// <summary>日轮 </summary>
        private Dictionary<int, LinkedList<WheelTask>> wheelDay;
        /// <summary>时轮 </summary>
        private Dictionary<int, LinkedList<WheelTask>> wheelHour;
        /// <summary>分轮 </summary>
        private Dictionary<int, LinkedList<WheelTask>> wheelMinute;
        /// <summary>秒轮 </summary>
        private Dictionary<int, LinkedList<WheelTask>> wheelSecond;
        /// <summary>毫秒轮 </summary>
        private Dictionary<int, LinkedList<WheelTask>> wheelMillisecond;

那么他们的分割情况具体是怎样的,请看下面:

定义常量

        private const int k_Level6 = 100;   //先来个百年奋斗目标,没人会定超过100年的定时器吧?
        private const int k_Level5 = 12;    //1年即12个月 分割成12份
        private const int k_Level4 = 31;    //1个月即31天 分割成31份
        private const int k_Level3 = 24;    //1天即24小时 分割成24份
        private const int k_Level2 = 60;    //1小时和1分钟 都分割成60份
        private const int k_Level1 = 10;    //1秒即1000毫秒 分割成10份,每份100毫秒

根据常量,初始化每个轮盘字典的槽位

为什么毫秒轮不按毫秒刻度1000来分?因为我不想让轮盘存的链表数量过多,所以只分了10份。

            //轮盘最大刻度,就从2023年开始到2123年吧 嘻嘻
            Enumerable.Range(2023, 2023 + k_Level6).ToList().ForEach(x =>
            {
                wheelYear.Add(x, new LinkedList<WheelTask>());
            });
            Enumerable.Range(0, k_Level5).ToList().ForEach(x =>
            {
                wheelMonth.Add(x, new LinkedList<WheelTask>());
            });
            Enumerable.Range(0, k_Level4).ToList().ForEach(x =>
            {
                wheelDay.Add(x, new LinkedList<WheelTask>());
            });
            Enumerable.Range(0, k_Level3).ToList().ForEach(x =>
            {
                wheelHour.Add(x, new LinkedList<WheelTask>());
            });
            Enumerable.Range(0, k_Level2).ToList().ForEach(x =>
            {
                wheelMinute.Add(x, new LinkedList<WheelTask>());
                wheelSecond.Add(x, new LinkedList<WheelTask>());
            });
            Enumerable.Range(0, k_Level1).ToList().ForEach(x =>
            {
                wheelMillisecond.Add(x, new LinkedList<WheelTask>());
            });

说完轮的定义和初始化之后,回到Update()函数里面来。

每次获取当前时间

DateTime now = DateTime.Now;

通过当前时间的年份,获取年轮对应槽位的双链表yearLinkedList

LinkedList<WheelTask> yearLinkedList = wheelYear[now.Year];

while循环判断双链表yearLinkedList是否有元素,若有则拿出第一个来,放到月轮的某个槽位数上的链表里,这个某槽位数就是任务里面保存的dateTime所在的月份。

  while (yearLinkedList.Any())
  {
      LinkedListNode<WheelTask> node = yearLinkedList.First;
      yearLinkedList.RemoveFirst();
      LinkedList<WheelTask> nextLinkedList = wheelMonth[node.Value.dateTime.Month];
      nextLinkedList.AddLast(node);
  }

同理,当前时间所在的月份,若月轮链表有元素,则将元素拿出来放到任务执行时间dateTime所在的日轮上。

  LinkedList<WheelTask> monthLinkedList = wheelMonth[now.Month];
  while (monthLinkedList.Any())
  {
      LinkedListNode<WheelTask> node = monthLinkedList.First;
      monthLinkedList.RemoveFirst();
      LinkedList<WheelTask> nextLinkedList = wheelDay[node.Value.dateTime.Day];
      nextLinkedList.AddLast(node);
  }

再同理,当前时间所在的日份,若日轮链表有元素,则将元素拿出来放到任务执行时间dateTime所在的时轮上。

  LinkedList<WheelTask> dayLinkedList = wheelDay[now.Day];
  while (dayLinkedList.Any())           
  {
      LinkedListNode<WheelTask> node = dayLinkedList.First;
      dayLinkedList.RemoveFirst();
      LinkedList<WheelTask> nextLinkedList = wheelHour[node.Value.dateTime.Hour];
      nextLinkedList.AddLast(node);
  }

同理,时轮、分轮的调度也是一样的。

只有秒轮和毫秒轮的处理有些不一样。

前面提到了,毫秒轮只分了10份,因此:

假设执行任务时间dateTime的毫秒值是456,那么该任务应该推向毫秒轮的哪个槽位呢?

槽位数索引从0开始算起,应该推向索引为4的槽位上,如下代码的逻辑所示:

    int x = k_Millisecond / k_Level1;   //把1000毫秒分割成10等份,每份占100毫秒
    LinkedList<WheelTask> secondLinkedList = wheelSecond[now.Second];    
    while (secondLinkedList.Any())
    {
        LinkedListNode<WheelTask> node = secondLinkedList.First;
        secondLinkedList.RemoveFirst();
        //当前毫秒值落在弟y等份,例如:当前毫秒50,则落在第0份上
        int nextY = node.Value.dateTime.Millisecond / x;
        LinkedList<WheelTask> nextLinkedList = wheelMillisecond[nextY];
        nextLinkedList.AddLast(node);
    }

有了这个秒轮的理解,那么毫秒轮的处理逻辑也不难理解了。

根据当前时间now的毫秒值,算出应该拿出哪个毫秒轮在处理。

处在毫秒轮的任务链表,都将会被执行掉。这个毫秒轮也就是前面提到的执行轮。

   int x = k_Millisecond / k_Level1;   //把1000毫秒分割成10等份,每份占100毫秒
   int y = now.Millisecond / x;        //当前毫秒值落在弟y等份,例如:当前毫秒50,则落在第0份上
   LinkedList<WheelTask> millisecondLinkedList = wheelMillisecond[y];
   while (millisecondLinkedList.Any())
   {
       foreach (WheelTask wheelTask in millisecondLinkedList)
       {
             if (DateTime.Now >= wheelTask.dateTime)
             {
                 millisecondLinkedList.Remove(wheelTask);
                 wheelTask.Run();
                 //wheelTask.RunAsync();
                 if (wheelTask.CheckLoop())
                 {
                     AddTask(wheelTask);
                 }
                 break;
              }
       }
   }

执行轮里面检测任务是否有循环,如果有,就重新添加这个任务。

在年轮上添加。

        private void AddTask(WheelTask wheelTask)
        {
            wheelYear[wheelTask.dateTime.Year].AddLast(wheelTask);
        }

既然有添加,那就有移除。做一个有手尾的人,不要让系统给你擦屁股。

        private bool RemoveTask(Dictionary<int, LinkedList<WheelTask>> wheel, int id)
        {
            foreach (var item in wheel)
            {
                LinkedList<WheelTask> wheelTasks = item.Value;
                foreach (var wheelTask in wheelTasks)
                {
                    if (wheelTask.id == id)
                    {
                        wheelTasks.Remove(wheelTask);
                        return true;
                    }
                }
            }
            return false;
        }

最后,提供两个方法暴露给外界调用定时器。

1.添加定时器任务。

        public int SetInterval(Action action, int id, int delay, int interval, int loopTimes)
        {
            lock(s_obj)
            {
                WheelTask wheelTask = new WheelTask(id, delay, interval, loopTimes, action);
                AddTask(wheelTask);
                return id;
            }
        }

2.清除定时器任务。

        public void ClearInterval(int id)
        {
            lock(s_obj)
            {
                if (RemoveTask(wheelYear, id))
                {
                    return;
                }
                if (RemoveTask(wheelMonth, id))
                {
                    return;
                }
                if (RemoveTask(wheelDay, id))
                {
                    return;
                }
                if (RemoveTask(wheelHour, id))
                {
                    return;
                }
                if (RemoveTask(wheelMinute, id))
                {
                    return;
                }
                if (RemoveTask(wheelSecond, id))
                {
                    return;
                }
                if (RemoveTask(wheelMillisecond, id))
                {
                    return;
                }
            }
        }

在主程序里,就可以使用了。

    public static void Test()
    {
        intervalId = timeWheel.SetInterval(Func3, 1, 1000, 2000, -1);
        timeWheel.SetInterval(Func2_Clear, 2, 6000, 1000, 1);
    }

    public static void Func2_Clear()
    {
        timeWheel.ClearInterval(intervalId);
        Console.WriteLine("定时执行了Func2_Clear  " + DateTime.Now + "   清除了定时任务" + intervalId);
    }

    public static void Func3()
    {
        Console.WriteLine("定时执行了Func3  " + DateTime.Now);
    }

程序输出结果如图:

 

最后的最后,奉上全部代码。

WheelTask.cs,TimeWheel.cs,主程序Program.cs

WheelTask类的全部代码。

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

namespace TimeWheelProject
{
    internal class WheelTask
    {
        /// <summary>任务唯一id </summary>
        public int id;
        /// <summary>延迟时间 </summary>
        public int delay;
        /// <summary>时间间隔 </summary>
        public int interval;
        /// <summary>循环次数 (-1则无限循环) </summary>
        public int loopTimes;
        /// <summary>执行时间 </summary>
        public DateTime dateTime;
        /// <summary>执行函数 </summary>
        public Action action;
        /// <summary>已循环的次数 </summary>
        private int m_hadLoopTimes;

        public WheelTask(int id, int delay, int interval, int loopTimes, Action action)
        {
            this.id = id;
            this.delay = delay;
            this.interval = interval;
            this.loopTimes = loopTimes;
            this.action = action;

            m_hadLoopTimes = 0;

            dateTime = DateTime.Now;
            dateTime = dateTime.AddMilliseconds(this.delay);
        }

        public void Run()
        {
            action();
        }

        public async Task RunAsync()
        {
            await Task.Run(() =>
            {
                action();
            });
        }

        public bool CheckLoop()
        {
            m_hadLoopTimes++;
            if (loopTimes == -1)
            {
                dateTime = DateTime.Now.AddMilliseconds(interval);
                return true;
            }
            else if(loopTimes > 0)
            {
                if (loopTimes > m_hadLoopTimes)
                {
                    dateTime = DateTime.Now.AddMilliseconds(interval);
                    return true;
                }
            }
            return false;
        }
    }
}

TimeWheel类的全部代码

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

namespace TimeWheelProject
{
    internal class TimeWheel
    {
        private const int k_Millisecond = 1000;

        private const int k_Level6 = 100;   //先来个百年奋斗目标,没人会定超过100年的定时器吧?
        private const int k_Level5 = 12;    //1年即12个月 分割成12份
        private const int k_Level4 = 31;    //1个月即31天 分割成31份
        private const int k_Level3 = 24;    //1天即24小时 分割成24份
        private const int k_Level2 = 60;    //1小时和1分钟 都分割成60份
        private const int k_Level1 = 10;    //1秒即1000毫秒 分割成10份,每份100毫秒

        private long m_temp = 0;
        private static object s_obj = new object();
        /// <summary>年轮 </summary>
        private Dictionary<int, LinkedList<WheelTask>> wheelYear;
        /// <summary>月轮 </summary>
        private Dictionary<int, LinkedList<WheelTask>> wheelMonth;
        /// <summary>日轮 </summary>
        private Dictionary<int, LinkedList<WheelTask>> wheelDay;
        /// <summary>时轮 </summary>
        private Dictionary<int, LinkedList<WheelTask>> wheelHour;
        /// <summary>分轮 </summary>
        private Dictionary<int, LinkedList<WheelTask>> wheelMinute;
        /// <summary>秒轮 </summary>
        private Dictionary<int, LinkedList<WheelTask>> wheelSecond;
        /// <summary>毫秒轮 </summary>
        private Dictionary<int, LinkedList<WheelTask>> wheelMillisecond;

        public TimeWheel() 
        {
            wheelYear = new Dictionary<int, LinkedList<WheelTask>>();
            wheelMonth = new Dictionary<int, LinkedList<WheelTask>>();
            wheelDay = new Dictionary<int, LinkedList<WheelTask>>();
            wheelHour = new Dictionary<int, LinkedList<WheelTask>>();
            wheelMinute = new Dictionary<int, LinkedList<WheelTask>>();
            wheelSecond = new Dictionary<int, LinkedList<WheelTask>>();
            wheelMillisecond = new Dictionary<int, LinkedList<WheelTask>>();

            //轮盘最大刻度,就从2023年开始到2123年吧 嘻嘻
            Enumerable.Range(2023, 2023 + k_Level6).ToList().ForEach(x =>
            {
                wheelYear.Add(x, new LinkedList<WheelTask>());
            });
            Enumerable.Range(0, k_Level5).ToList().ForEach(x =>
            {
                wheelMonth.Add(x, new LinkedList<WheelTask>());
            });
            Enumerable.Range(0, k_Level4).ToList().ForEach(x =>
            {
                wheelDay.Add(x, new LinkedList<WheelTask>());
            });
            Enumerable.Range(0, k_Level3).ToList().ForEach(x =>
            {
                wheelHour.Add(x, new LinkedList<WheelTask>());
            });
            Enumerable.Range(0, k_Level2).ToList().ForEach(x =>
            {
                wheelMinute.Add(x, new LinkedList<WheelTask>());
                wheelSecond.Add(x, new LinkedList<WheelTask>());
            });
            Enumerable.Range(0, k_Level1).ToList().ForEach(x =>
            {
                wheelMillisecond.Add(x, new LinkedList<WheelTask>());
            });
        }

        public void Update()
        {
            lock (s_obj)
            {
                DateTime now = DateTime.Now;
                if (now.Ticks - m_temp >= 10000000)
                {
                    Console.WriteLine("当前时间 " + now);
                    m_temp = now.Ticks;
                }

                LinkedList<WheelTask> yearLinkedList = wheelYear[now.Year];
                LinkedList<WheelTask> monthLinkedList = wheelMonth[now.Month];
                LinkedList<WheelTask> dayLinkedList = wheelDay[now.Day];
                LinkedList<WheelTask> hourLinkedList = wheelHour[now.Hour];
                LinkedList<WheelTask> minuteLinkedList = wheelMinute[now.Minute];
                LinkedList<WheelTask> secondLinkedList = wheelSecond[now.Second];

                int x = k_Millisecond / k_Level1;   //把1000毫秒分割成10等份,每份占100毫秒
                int y = now.Millisecond / x;        //当前毫秒值落在弟y等份,例如:当前毫秒50,则落在第0份上
                LinkedList<WheelTask> millisecondLinkedList = wheelMillisecond[y];

                while (yearLinkedList.Any())
                {
                    LinkedListNode<WheelTask> node = yearLinkedList.First;
                    yearLinkedList.RemoveFirst();
                    LinkedList<WheelTask> nextLinkedList = wheelMonth[node.Value.dateTime.Month];
                    nextLinkedList.AddLast(node);
                }

                while (monthLinkedList.Any())
                {
                    LinkedListNode<WheelTask> node = monthLinkedList.First;
                    monthLinkedList.RemoveFirst();
                    LinkedList<WheelTask> nextLinkedList = wheelDay[node.Value.dateTime.Day];
                    nextLinkedList.AddLast(node);
                }

                while (dayLinkedList.Any())
                {
                    LinkedListNode<WheelTask> node = dayLinkedList.First;
                    dayLinkedList.RemoveFirst();
                    LinkedList<WheelTask> nextLinkedList = wheelHour[node.Value.dateTime.Hour];
                    nextLinkedList.AddLast(node);
                }

                while (hourLinkedList.Any())
                {
                    LinkedListNode<WheelTask> node = hourLinkedList.First;
                    hourLinkedList.RemoveFirst();
                    LinkedList<WheelTask> nextLinkedList = wheelMinute[node.Value.dateTime.Minute];
                    nextLinkedList.AddLast(node);
                }

                while (minuteLinkedList.Any())
                {
                    LinkedListNode<WheelTask> node = minuteLinkedList.First;
                    minuteLinkedList.RemoveFirst();
                    LinkedList<WheelTask> nextLinkedList = wheelSecond[node.Value.dateTime.Second];
                    nextLinkedList.AddLast(node);
                }

                while (secondLinkedList.Any())
                {
                    LinkedListNode<WheelTask> node = secondLinkedList.First;
                    secondLinkedList.RemoveFirst();
                    //当前毫秒值落在弟y等份,例如:当前毫秒50,则落在第0份上
                    int nextY = node.Value.dateTime.Millisecond / x;
                    LinkedList<WheelTask> nextLinkedList = wheelMillisecond[nextY];
                    nextLinkedList.AddLast(node);
                }

                while (millisecondLinkedList.Any())
                {
                    foreach (WheelTask wheelTask in millisecondLinkedList)
                    {
                        if (DateTime.Now >= wheelTask.dateTime)
                        {
                            millisecondLinkedList.Remove(wheelTask);
                            wheelTask.Run();
                            //wheelTask.RunAsync();
                            if (wheelTask.CheckLoop())
                            {
                                AddTask(wheelTask);
                            }
                            break;
                        }
                    }
                }
            }
        }

        private void AddTask(WheelTask wheelTask)
        {
            wheelYear[wheelTask.dateTime.Year].AddLast(wheelTask);
        }

        private bool RemoveTask(Dictionary<int, LinkedList<WheelTask>> wheel, int id)
        {
            foreach (var item in wheel)
            {
                LinkedList<WheelTask> wheelTasks = item.Value;
                foreach (var wheelTask in wheelTasks)
                {
                    if (wheelTask.id == id)
                    {
                        wheelTasks.Remove(wheelTask);
                        return true;
                    }
                }
            }
            return false;
        }

        public int SetInterval(Action action, int id, int delay, int interval, int loopTimes)
        {
            lock(s_obj)
            {
                WheelTask wheelTask = new WheelTask(id, delay, interval, loopTimes, action);
                AddTask(wheelTask);
                return id;
            }
        }

        public void ClearInterval(int id)
        {
            lock(s_obj)
            {
                if (RemoveTask(wheelYear, id))
                {
                    return;
                }
                if (RemoveTask(wheelMonth, id))
                {
                    return;
                }
                if (RemoveTask(wheelDay, id))
                {
                    return;
                }
                if (RemoveTask(wheelHour, id))
                {
                    return;
                }
                if (RemoveTask(wheelMinute, id))
                {
                    return;
                }
                if (RemoveTask(wheelSecond, id))
                {
                    return;
                }
                if (RemoveTask(wheelMillisecond, id))
                {
                    return;
                }
            }
        }
    }
}

主程序入口Program 代码

using TimeWheelProject;

internal class Program
{
    public static TimeWheel timeWheel;
    private static Thread thread;
    private static int intervalId;
    private static void Main(string[] args)
    {
        timeWheel = new TimeWheel();
        thread = new Thread(TimeWheelProgram);
        thread.Start();

        Test();

        Console.ReadLine();
    }

    public static void TimeWheelProgram()
    {
        while (true)
        {
            timeWheel.Update();
        }
    }

    public static void Test()
    {
        intervalId = timeWheel.SetInterval(Func3, 1, 1000, 2000, -1);
        timeWheel.SetInterval(Func2_Clear, 2, 6000, 1000, 1);
    }

    public static void Func1()
    {
        int a = 0;
        for (int i = 0; i < 100000; i++)
        {
            for (int j = 0; j < 100000; j++)
            {
                a++;
            }
        }
        Console.WriteLine("定时执行了Func1  " + DateTime.Now + "   a = " +  a);
    }

    public static void Func2_Clear()
    {
        timeWheel.ClearInterval(intervalId);
        Console.WriteLine("定时执行了Func2_Clear  " + DateTime.Now + "   清除了定时任务" + intervalId);
    }

    public static void Func3()
    {
        Console.WriteLine("定时执行了Func3  " + DateTime.Now);
    }
}

终于,结束了~~~

<think>根据用户的问题,用户想要在C#实现await与定时器结合的用法。结合引用内容,我们可以提供几种使用异步等待(await)实现定时器功能的方法。引用[2]展示了使用Task.Delay在特定时间点执行方法:-计算目标时间与当前时间的差值作为延迟时间-使用awaitTask.Delay(delay)等待指定时间-然后执行目标方法引用[3]和[4]提到了三种定时器实现方式:1.System.Threading.Timer2.使用Thread和Sleep3.使用Task和Delay(异步方式)引用[5]提到了时间轮定时器,但用户需要的是简单await定时器。结合用户需求,重点推荐异步友好的Task.Delay方式,并补充Timer的异步封装。以下是具体实现方法:</think>### C# 中 await 与定时器结合的实现方法 在 C# 中结合 `await` 和定时器实现异步定时任务,主要有以下三种推荐方案: #### 1. 使用 `Task.Delay` + 异步循环(推荐) ```csharp async Task RunPeriodicAsync(Func<Task> action, TimeSpan interval) { while (true) { await Task.Delay(interval); // 等待指定间隔 await action(); // 执行异步操作 } } // 使用示例 async Task Main() { var interval = TimeSpan.FromSeconds(5); await RunPeriodicAsync(async () => { Console.WriteLine($"执行于 {DateTime.Now:HH:mm:ss}"); // 添加你的异步操作(如 API 调用) }, interval); } ``` **特点**: - 纯异步实现,不阻塞线程 - 自动等待上次操作完成后再开始下次计时 - 可通过 `CancellationToken` 添加退出机制[^2][^3] #### 2. 封装 `System.Threading.Timer` 为异步 ```csharp class AsyncTimer { private readonly Func<Task> _callback; private readonly Timer _timer; public AsyncTimer(Func<Task> callback, TimeSpan dueTime, TimeSpan period) { _callback = callback; _timer = new Timer(_ => callback().Wait(), null, dueTime, period); } // 使用异步回调的改进版 public static Timer Create(Func<Task> callback, TimeSpan dueTime, TimeSpan period) { return new Timer(async _ => await callback(), null, dueTime, period); } } // 使用示例 var timer = AsyncTimer.Create( callback: async () => await LogStatusAsync(), dueTime: TimeSpan.Zero, period: TimeSpan.FromMinutes(1) ); ``` **注意**:需要处理回调中的异常,避免导致进程崩溃[^4] #### 3. 使用 `PeriodicTimer` (.NET 6+ 专属) ```csharp async Task UsePeriodicTimerAsync() { using var timer = new PeriodicTimer(TimeSpan.FromSeconds(10)); while (await timer.WaitForNextTickAsync()) { try { await ProcessDataAsync(); // 执行异步任务 } catch (Exception ex) { // 异常处理 } } } ``` **优势**: - 专为异步设计,无回调嵌套问题 - 天然支持 `CancellationToken` - 轻量级且线程安全[^5] ### 关键选择建议 | 方法 | 适用场景 | 注意事项 | |--------------------|-----------------------------------|-----------------------------| | `Task.Delay` 循环 | 简单周期任务,需等待操作完成 | 避免长时间阻塞操作 | | 异步封装 Timer | 兼容旧版 .NET | 需手动处理线程安全和异常 | | `PeriodicTimer` | .NET 6+ 新项目 | 不支持 .NET Framework | **重要提示**: 1. 定时操作中**必须捕获异常**,防止未处理异常终止定时器 2. 长时间运行操作需考虑并发控制(如使用 `SemaphoreSlim`) 3. 使用 `CancellationTokenSource` 实现优雅停止: ```csharp var cts = new CancellationTokenSource(); await RunPeriodicAsync(MyTask, interval, cts.Token); // 需要停止时调用 cts.Cancel() ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值