日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > C# >内容正文

C#

基于C#的计时管理器

發布時間:2023/12/4 C# 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 基于C#的计时管理器 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

問題

我們使用各種系統時候會遇到以下問題:

  • 12306上購買火車票如果15分鐘內未完成支付則訂單自動取消。

  • 會議場館預定座位,如果10分鐘內未完成支付則預定自動取消。

  • 在指定時間之后,我需要執行一項任務。

我之前做的很多系統,往往都是定期執行一個特定任務。而上訴問題都涉及到滑動窗口時間的定時任務。

比如:我早上10點20分預定了一張火車票,我需要在15分鐘內支付完成,否則訂單會被取消。同一時間可能會有成百上千的人預定其他火車票,我需要在每個人的15分鐘期限達時候執行檢查,如果還未支付則自動取消訂單。

方案

我們搞清楚了要解決的問題以后,我們來思考方案。有經驗的程序員會立即思考出下面的方案:

  • 使用消息隊列的延遲投送功能,每個訂單添加成功后發送一個延遲15分鐘的延遲消息。訂單狀態處理器15分鐘后收到消息,檢查支付狀態,如果未支付則取消訂單。

  • Redis也有類似的功能,原理大致相同。

但我不想使用消息隊列的功能,因為延遲消息投送是一種技術實現,我希望用代碼反應這種業務實現,所以用純代碼來處理他。(我并不是為了從新發明車輪,因為這是一種業務需求,會有變化擴展的需要,所以決定自己嘗試做一下增加經驗)

算法思路:

我們的需求定時時間都在15分鐘以內,假定都沒有超過1個小時的或者幾天的。(如果超過1個小時的,可以擴展這個設計,這篇暫時不展開討論)

我們可考慮將一個小時分成3600秒,每秒代表一個位置來存儲所有到期的訂單,當下單的時候根據當前時間 加上 15分鐘時間間隔,我們就可以得到15分鐘以后的時間,將這個訂單添加到對應的位置上。

數據結構選擇:

我們選擇C#中提供的最新的并發字典作為基礎數據結構,Key值是3600秒中的每一秒的數值,內容是一個隊列用于存放該時間點的所有訂單。

public ConcurrentDictionary<int, ConcurrentQueue<IJob>> jobs = new ConcurrentDictionary<int, ConcurrentQueue<IJob>>();

數據結構我們思考好了,其實功能就完成了大半了,代碼的設計也就基本定下來了。

代碼實現(我喜歡使用控制臺應用程序做實驗)
  • 建立一個定時管理器

    public class TimerManager{//并發字典存儲需要檢查的任務(這里可以是訂單檢查任務,每個任務可以包含一個訂單Id)public ConcurrentDictionary<int, ConcurrentQueue<IJob>> jobs = new ConcurrentDictionary<int, ConcurrentQueue<IJob>>();private Timer timer;public TimerManager(){//每間隔1秒鐘執行一次。和當前時間同步。timer = new Timer(ProcessJobs, null, 0, 1000);}}
  • 增加一個任務到字典

    /// <summary>/// 增加一個任務到時間字典中/// </summary>/// <param name="timeKey">根據延遲時間計算出的key值</param>/// <param name="duetime">毫秒單位</param>/// <exception cref="NotImplementedException"></exception>public void AddJob(IJob job, TimeSpan duetime){var key = GetKey(duetime);ConcurrentQueue<IJob> queue = new ConcurrentQueue<IJob>();queue.Enqueue(job);jobs.AddOrUpdate(key, queue, (key, jobs) =>{jobs.Enqueue(job);return jobs;});}
  • 根據時間計算Key的方法

    /// <summary>/// 根據延遲時間生成當前鍵值/// </summary>/// <param name="duetime"></param>/// <returns></returns>private int GetKey(TimeSpan duetime){var currentDateTime = DateTime.Now;//到期時間var targetDateTime = currentDateTime.Add(duetime);//不要忘了把分鐘換算成秒,然后在和延遲時間相加就得到Keyvar key = targetDateTime.Minute * 60 + targetDateTime.Second;return key;}
  • 將任務添加到字典

    /// <summary>/// 增加一個任務到時間字典中/// </summary>/// <param name="job">需要執行的任務</param>/// <param name="duetime">多少時間間隔后檢查</param>public void AddJob(IJob job, TimeSpan duetime){var key = GetKey(duetime);ConcurrentQueue<IJob> queue = new ConcurrentQueue<IJob>();queue.Enqueue(job);//這是并發字典的方法,這里就是當Key不存在就增加新的值進去,當Key存在就在Key的隊列中增加一個新任務jobs.AddOrUpdate(key, queue, (key, jobs) =>{jobs.Enqueue(job);return jobs;});}
  • 計時器每秒執行時處理任務的方法,循環從隊列中取出任務直到所有任務處理完畢。

    private async void ProcessJobs(object state){//根據當前時間計算Key值var key = DateTime.Now.Minute * 60 + DateTime.Now.Second;Console.WriteLine(key);//查找Key值對應的任務隊列并處理。bool keyExists = jobs.TryGetValue(key, out var jobQueue);if (keyExists){IJob job;while(jobQueue.TryDequeue(out job)){await job.Run();}}}
  • 代碼中設計IJob 和Job的一個實現,為了易于理解,這個job沒有做太多事情。如果需要擴展去檢查訂單,可以在這里記錄訂單Id,創建任務的時候將訂單ID和任務關聯,這樣定時器處理這個任務的時候能找到對應訂單了。

    public interface IJob{Task Run();}/// <summary>/// 代表一個工作/// </summary>public class Job : IJob{public Guid JobId { get; set; }public Job(){JobId = Guid.NewGuid();}public async Task Run(){Console.WriteLine(" Job Id: " + JobId.ToString() + " is running.");await Task.Delay(2000);Console.WriteLine(" Job Id:" + JobId.ToString() + " have completed.");}}
  • 主程序Programe中調用定時管理器

    using TimerTest;Console.WriteLine("Hello, World!");TimerManager timerManager = new TimerManager();Job job1 = new Job();// 添加一個任務1分鐘后執行 timerManager.AddJob( job1, TimeSpan.FromMinutes(1));// 在添加另一個任務2分鐘后執行 Job job2 = new Job(); timerManager.AddJob(job2, TimeSpan.FromMinutes(2));Console.ReadLine();
  • 執行結果

    結果中可以看到, 任務1 在1014的鍵值上被處理,1014的鍵值對應的時間是 16:54 秒,也就是在我運行這個程序1分鐘后。

    任務添加時間執行時間
    第一次任務(計時1分鐘)15:5416:54
    第二次任務(計時2分鐘)15:5417:54

    任務2 在 1074的鍵值上被處理,1074對應的時間是 17:54 秒 執行。從上表可以看出程序正常運行得出結果。

    總結

    這是一個簡單的控制臺程序驗證了這個定時管理器的實現方法,我們將1個小時分成3600秒,每一秒對應一個Key值,在這個值上我們存儲需要被處理的任務。在增加任務時候,我們也用同樣的算法確定這個Key值。處理的時候根據當前時間計算除Key值進行處理。

    這樣的話,在真實場景中,我們有3600個Key值可以存儲每一秒鐘用戶提交的所有訂單,時間沒走過1秒我就處理對應的任務。

    后續可以完善的地方
    • 我們可以將這個類添加到ASP.NET MVC中,使用依賴注入為單實例生命周期,并發字典和并發隊列是線程安全的,所以這里可以放心使用。

    • 我們可以擴展Job方法,根據業務邏輯添加更多的信息以便于處理。例如處理訂單的ID,或其他什么業務ID。

    • 處理任務的方法可以采用多個消費者并發執行,增加處理速度。

    • 可以將任務實體存儲到數據庫,以便于應對突發宕機事故可以快速重建任務。

    • 當然我們也可以用Hangfire來輕松實現這個業務。

      var jobId = BackgroundJob.Schedule(() => Console.WriteLine("Delayed!"),TimeSpan.FromDays(7)); //這里改成分鐘就好了

    最后祝.NET 20周年快樂。

    總結

    以上是生活随笔為你收集整理的基于C#的计时管理器的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。