解決一個C#中定時任務被阻塞問題

JerryMouseLi發表於2021-11-12

解決一個C#中定時任務被阻塞問題

1.摘要

本文會介紹一個C#中最簡單定時任務的使用方法,以及會遇到的定時任務被阻塞現象,從筆者理解的角度分析原因。以及提供解決方案。

2.C#中定時任務的最簡方法

  protected internal void PollClient()
  {
      int i=0;
      Timer t = new Timer(p => {
          i++;
          if (deviceContextList.Count > 0)
          {
              var deviceContext=GetDeviceContext("123456789");
                  SendMessage(messageList[i%7],deviceContext.tcpSession.writerContext);
              logger.Info("客戶端數量:"+ deviceContextList.Count);        
          }
          else 
          {
              logger.Info("客戶端數量為0");
              Console.WriteLine("客戶端數量為0");
          }              
      }, null, 0, 1000) ;           
  }

上面的timer方法提供於微軟System.Threading名稱空間。System.Threading.Timer 是由執行緒池呼叫的。所有的Timer物件只使用了一個執行緒來管理。這個執行緒知道下一個回撥物件在什麼時候到期。下一個回撥物件到期時,執行緒就會喚醒,在內部呼叫ThreadPool 的 QueueUserWorkItem,將一個工作項新增到執行緒池佇列中,使你的回撥方法得到呼叫。此方法有多個過載,具體讀者可以自行去看。

Timer(TimerCallback callback, object state, int dueTime, int period)

第一個引數callback是回撥方法,第二個引數state可以傳參給回撥方法的引數,第三個引數dueTime是第一次執行回撥函式的延時時間,單位毫秒,第四個引數period是呼叫回撥函式的時間間隔。使用起來是不是特別方便,把你需要執行的定時任務放在回撥方法中,可獨立寫成方法,也可像上面一樣寫成匿名方法的形式。

3.定時任務阻塞現象

當上述任務被執行了幾千次以後,定時任務會阻塞,不再執行,也不再列印日誌。並且上面的寫法有缺陷,。如果回撥方法的執行時間很長,計時器可能(在上個回撥還沒有完成的時候)再次觸發。這可能造成多個執行緒池執行緒同時執行你的回撥方法。並且執行緒切換也會造成諸多損耗時間。

4.阻塞現象原因分析

上面的方法中使用區域性變數來建立指向一個執行緒定時器。因為區域性變數會被GC回收,導致定時器失效。
具體改進如下:

static int i=0;
static Timer _timer = null;
        protected  void PollClient()
        {
             _timer = new Timer(TimerCallback, null, 1000, Timeout.Infinite) ;           
        }
    private void TimerCallback(object state)
    {
            try
            {
                i++;
              
                if (deviceContextList.Count > 0)
                {
                    var deviceContext = GetDeviceContext("123456789");
                    SendMessage(messageList[i % 7], deviceContext.tcpSession.writerContext);
                    logger.Info("客戶端數量:" + deviceContextList.Count + "迴圈次數:" + i);

                }
                else
                {
                    logger.Info("客戶端數量為0" + "迴圈次數:" + i);
                    Console.WriteLine("客戶端數量為0" + "迴圈次數:" + i);
                }
            }
            catch (Exception e)
            {
                logger.Error("定時測試下發報文異常:" + e);
            }
            finally
            {
                _timer.Change( 1000, Timeout.Infinite);
            }
        }

將定時器與計數變數設定為static是為了定時器不被GC回收。定時任務執行完成之後再設定下次呼叫時間間隔是為了該任務不過多佔用執行緒池中的執行緒,節省執行緒切換時間等。

5.問題解決

可以看到任務已經被執行了86665次,優化後不再被GC回收。


版權宣告:本文為博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連結和本宣告。 本文連結:https://www.cnblogs.com/JerryMouseLi/p/15543495.html

相關文章