【轉】最近用Timer踩了一個坑,分享一下避免別人繼續踩

来源:https://www.cnblogs.com/RYouHoo-923/archive/2018/01/12/8274277.html
-Advertisement-
Play Games

【轉】最近用Timer踩了一個坑,分享一下避免別人繼續踩 最近做一個小項目,項目中有一個定時服務,需要向對方定時發送數據,時間間隔是1.5s,然後就想到了用C#的Timer類,我們知道Timer 確實非常好用,因為裡面有非常人性化的start和stop功能,在Timer裡面還有一個Interval, ...


【轉】最近用Timer踩了一個坑,分享一下避免別人繼續踩

 

  最近做一個小項目,項目中有一個定時服務,需要向對方定時發送數據,時間間隔是1.5s,然後就想到了用C#的Timer類,我們知道Timer

確實非常好用,因為裡面有非常人性化的start和stop功能,在Timer裡面還有一個Interval,就是用來設置時間間隔,然後時間間隔到了就會觸

發Elapsed事件,我們只需要把callback函數註冊到這個事件就可以了,如果Interval到了就會觸發Elapsed,貌似一切看起來很順其自然,但是

有一點一定要註意,callback函數本身執行也是需要時間的,也許這個時間是1s,2s或者更長時間,而timer類卻不管這些,它只顧1.5s觸發一下

Elapsed,這就導致了我的callback可能還沒有執行完,下一個callback又開始執行了,也就導致了沒有達到我預期的1.5s的效果,並且還出現了

一個非常嚴重的問題,那就是線程激增,非常恐怖。

 

   下麵舉個例子,為了簡化一下,我就定義一個task任務,當然項目中是多個task任務一起跑的。

 

一:問題產生

   為了具有更高的靈活性,我定義了一個CustomTimer類繼承自Timer,然後裡面可以放些Task要跑的數據,這裡就定義一個Queue。

 1 namespace Sample
 2 {
 3     class Program
 4     {
 5         static void Main(string[] args)
 6         {
 7             TimerCustom timer = new TimerCustom();
 8 
 9             timer.Interval = 1500;
10 
11             timer.Elapsed += (obj, evt) =>
12             {
13                 TimerCustom singleTimer = obj as TimerCustom;
14 
15                 if (singleTimer != null)
16                 {
17                     if (singleTimer.queue.Count != 0)
18                     {
19                         var item = singleTimer.queue.Dequeue();
20 
21                         Send(item);
22                     }
23                 }
24             };
25 
26             timer.Start();
27 
28             Console.Read();
29         }
30 
31         static void Send(int obj)
32         {
33             //隨機暫定8-10s
34             Thread.Sleep(new Random().Next(8000, 10000));
35 
36             Console.WriteLine("當前時間:{0},定時數據發送成功!", DateTime.Now);
37         }
38     }
39 
40     class TimerCustom : System.Timers.Timer
41     {
42         public Queue<int> queue = new Queue<int>();
43 
44         public TimerCustom()
45         {
46             for (int i = 0; i < short.MaxValue; i++)
47             {
48                 queue.Enqueue(i);
49             }
50         }
51     }
52 }

 

二:解決方法

1.  從上圖看,在一個任務的情況下就已經有14個線程了,並且在21s的時候有兩個線程同時執行了,我的第一反應就是想怎麼把後續執行callback的

線程踢出去,也就是保證當前僅讓兩個線程在用callback,一個在執行,一個在等待執行,如果第一個線程的callback沒有執行完,後續如果來了第三

個線程的話,我就把這第三個線程直接踢出去,直到第一個callback執行完後,才允許第三個線程進來並等待執行callback,然後曾今的第二個線程開

始執行callback,後續的就以此類推。。。

然後我就想到了用lock機制,在customTimer中增加lockMe,lockNum,isFirst欄位,用lockMe來鎖住,用lockNum來踢當前多餘的要執行callback

的線程,用isFirst來判斷是不是第一次執行該callback,後續callback的線程必須先等待1.5s再執行。

 1 namespace Sample
 2 {
 3     class Program
 4     {
 5         static void Main(string[] args)
 6         {
 7             TimerCustom timer = new TimerCustom();
 8 
 9             timer.Interval = 1500;
10 
11             timer.Elapsed += (obj, evt) =>
12             {
13                 TimerCustom singleTimer = obj as TimerCustom;
14 
15                 if (singleTimer != null)
16                 {
17                     //如果當前等待線程>2,就踢掉該線程
18                     if (Interlocked.Read(ref singleTimer.lockNum) > 2)
19                         return;
20 
21                     Interlocked.Increment(ref singleTimer.lockNum);
22 
23                     //這裡的lock只能存在一個線程等待
24                     lock (singleTimer.lockMe)
25                     {
26                         if (!singleTimer.isFirst)
27                         {
28                             Thread.Sleep((int)singleTimer.Interval);
29                         }
30 
31                         singleTimer.isFirst = false;
32 
33                         if (singleTimer.queue.Count != 0)
34                         {
35                             var item = singleTimer.queue.Dequeue();
36 
37                             Send(item);
38 
39                             Interlocked.Decrement(ref singleTimer.lockNum);
40                         }
41                     }
42                 }
43             };
44 
45             timer.Start();
46 
47             Console.Read();
48         }
49 
50         static void Send(int obj)
51         {
52             Thread.Sleep(new Random().Next(8000, 10000));
53 
54             Console.WriteLine("當前時間:{0},郵件發送成功!", DateTime.Now);
55         }
56     }
57 
58     class TimerCustom : System.Timers.Timer
59     {
60         public Queue<int> queue = new Queue<int>();
61 
62         public object lockMe = new object();
63 
64         public bool isFirst = true;
65 
66         /// <summary>
67         /// 為保持連貫性,預設鎖住兩個
68         /// </summary>
69         public long lockNum = 0;
70 
71         public TimerCustom()
72         {
73             for (int i = 0; i < short.MaxValue; i++)
74             {
75                 queue.Enqueue(i);
76             }
77         }
78     }
79 }

 

 

從圖中可以看到,已經沒有同一秒出現重覆任務的發送情況了,並且線程也給壓制下去了,乍一看效果不是很明顯,不過這是在一個任務的情況

下的場景,任務越多就越明顯了,所以這個就達到我要的效果。

 

2. 從上面的解決方案來看,其實我們的思維已經被問題約束住了,當時我也是這樣,畢竟坑出來了,就必須來填坑,既然在callback中出現線程

  蜂擁的情況,我當然要想辦法管制了,其實這也沒什麼錯,等問題解決了再回頭考慮下時,我們會發現文章開頭說的Timer類有強大的Stop和

   Start功能,所以。。。。這個時候思維就跳出來了,何不在callback執行的時候把Timer關掉,執行完callback後再把Timer開啟,這樣不就

   可以解決問題嗎?好吧,說乾就乾。

 1 namespace Sample
 2 {
 3     class Program
 4     {
 5         static void Main(string[] args)
 6         {
 7             TimerCustom timer = new TimerCustom();
 8 
 9             timer.Interval = 1500;
10 
11             timer.Elapsed += (obj, evt) =>
12             {
13                 TimerCustom singleTimer = obj as TimerCustom;
14 
15                 //先停掉
16                 singleTimer.Stop();
17 
18                 if (singleTimer != null)
19                 {
20                     if (singleTimer.queue.Count != 0)
21                     {
22                         var item = singleTimer.queue.Dequeue();
23 
24                         Send(item);
25 
26                         //發送完成之後再開啟
27                         singleTimer.Start();
28                     }
29                 }
30             };
31 
32             timer.Start();
33 
34             Console.Read();
35         }
36 
37         static void Send(int obj)
38         {
39             Thread.Sleep(new Random().Next(8000, 10000));
40 
41             Console.WriteLine("當前時間:{0},郵件發送成功!", DateTime.Now);
42         }
43     }
44 
45     class TimerCustom : System.Timers.Timer
46     {
47         public Queue<int> queue = new Queue<int>();
48 
49         public object lockMe = new object();
50 
51         /// <summary>
52         /// 為保持連貫性,預設鎖住兩個
53         /// </summary>
54         public long lockNum = 0;
55 
56         public TimerCustom()
57         {
58             for (int i = 0; i < short.MaxValue; i++)
59             {
60                 queue.Enqueue(i);
61             }
62         }
63     }
64 }

 

從圖中可以看到,問題同樣得到解決,而且更簡單,精妙。


最後總結一下:解決問題的思維很重要,但是如果跳出思維站到更高的抽象層次上考慮問題貌似也很難得。。。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 概述 在之前寫的一篇關於async和await的前世今生的文章之後,大家似乎在async和await提高網站處理能力方面還有一些疑問,博客園本身也做了不少的嘗試。今天我們再來回答一下這個問題,同時我們會做一個async和await在WinForm中的嘗試,並且對比在4.5之前的非同步編程模式APM/E ...
  • 一個網頁,它是顯示圖片,但在一些瀏覽器,它卻顯示如下: Insus.NET猜,不是瀏覽器不相容,就是代碼有問題。 在代碼中,只是輸出數據流,圖片格式很多種,如jpg,png,bmp等,沒有指定,程式也不清楚要顯示什麼格式的圖片。因此,Insus.NET把代碼改為如下: context.Respons ...
  • 為什麼要學習表達式樹?表達式樹是將我們原來可以直接由代碼編寫的邏輯以表達式的方式存儲在樹狀的結構里,從而可以在運行時去解析這個樹,然後執行,實現動態的編輯和執行代碼。LINQ to SQL就是通過把表達式樹翻譯成SQL來實現的,所以瞭解表達樹有助於我們更好的理解 LINQ to SQL,同時如果你有 ...
  • 快樂的Lambda表達式 上一篇 背後的故事之 - 快樂的Lambda表達式(一)我們由淺入深的分析了一下Lambda表達式。知道了它和委托以及普通方法的區別,並且通過測試對比他們之間的性能,然後我們通過IL代碼深入瞭解了Lambda表達式,以及介紹瞭如何在.NET中用Lambda表達式來實現Jav ...
  • 快樂的Lambda表達式(二) 自從Lambda隨.NET Framework3.5出現在.NET開發者眼前以來,它已經給我們帶來了太多的欣喜。它優雅,對開發者更友好,能提高開發效率,天啊!它還有可能降低發生一些潛在錯誤的可能。LINQ包括ASP.NET MVC中的很多功能都是用Lambda實現的。 ...
  • C#集體類型( Collections in C#) 集合是.NET FCL(Framework Class Library)中很重要的一部分,也是我們開發當中最常用到的功能之一,幾乎是無處不在。俗話說知其然,知其所以然,平常看到IEnumerable,IEnumerator,ICollection ...
  • NanUI文檔目錄 "NanUI簡介" "開始使用NanUI" "打包並使用內嵌式的HTML/CSS/JS資源" "使用網頁來設計整個視窗" "如何實現C 與Javascript的相互通信" 如何處理NanUI中的下載過程 DonwloadHandler的使用 (待更新。。。) 如何處理NanUI中 ...
  • 目前開發的所有代碼都已經上傳到了GitHub。歡迎大家來Star https://github.com/GiantLiu/AutoJump 目前程式分為“全自動版本”和“半自動版本” 全自動版本 WeChat.AutoJump.CMDApp 當手機連接好後,打開微信跳一跳 點擊"開始游戲"後。運行此 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...