.Net Core 簡單定時任務框架封裝

来源:https://www.cnblogs.com/osscoder/archive/2018/11/30/10036062.html
-Advertisement-
Play Games

​這篇文章還是回到實際的基礎封裝過程實現層面,用一個小東西來演示如何在常見業務代碼中梳理職責 ...


  有段日子沒有更新,寫點東西冒個泡 。這篇文章過來講個小東西,也是大家在日常開發中也經常需要面臨的問題:後臺定時任務處理。估計大家看到這句就已經聯想到 QuartZ 等類似第三方類庫了,不好意思,後邊的事情和它們沒有關係。這裡要展開的是用.Net Core 下的 Generic Host 配合封裝簡版定時任務處理框架的過程。至於什麼是Generic Host,簡單來說就是一個簡化版不含Http管道等的非Web應用托管宿主服務,至於它如何來,其內有著什麼樣的實現細節,官方介紹已經足夠。這篇文章主要還是回到實際的基礎封裝過程實現層面,用一個小東西來演示如何在常見業務代碼中梳理職責,內容主要如下:

1.  概要分解

2.  封裝實現

3.   示例演示

4.   註意事項

 一. 概要分解

  如果對Generic Host 已經有瞭解的同學可能也看過網上其他文章,大多也都介紹用它如何實現定時任務處理。這些文章基本提供了一個通用實現,對業務實現還是稍顯啰嗦。這兩天整理邏輯有個任務不得不臨時定時處理,想到這個東西,花了點時間處理了下,東西不複雜不過還是想把這個思路分享給需要的朋友。

  定時任務,分解來看特別簡單,就是兩個維度“  定時 +  任務 ”,如果還有另外一個維度,那就是 任務運行的托管服務。在托管平臺上添加定時規則,根據規則觸發任務,工作結束。

  1.  關於定時,主要就是一套任務觸發的規則,其作為一個調度者,只需要關心的是 在什麼時間,以何種頻率 觸發任務。   在.Net 下我們通過定時器(Timer - 構造函數包含這兩個核心參數,.net 下有兩個Timer實現,一個是System.Timer.Timer,一個是System.Threading.Timer, 這裡用的第二者,自由度更高)來實現,但是它不應該直接和具體的任務掛鉤,使用方也不應該每次都自己來處理Timer的初始化及相關回收釋放等相同操作,我們需要的是使用方只需告知框架層要執行什麼任務,和任務對應的時間規則。

  2.  關於任務, 這個角色是一個任務的執行者, 定時調度者 告訴 任務執行者 在什麼時候開始執行和結束任務,其本身不會關註調度的實現。

  3.  關於托管服務,也就是已經說過的Generic Host,當然你也可以使用windows服務等。它的職責就是保證給任務提供執行環境,並告訴任務定時器當前服務在什麼時候開始運行和關閉。  實現時提供了統一 IHostedService  介面,具體實現下邊實現會有展示。Generic Host 啟動方式有兩種形式:

    a. 如果是.NetCore 站點,預設已經包含,只需要在 ConfigureServices 時註冊具體實現即可。

    b. 可以獨立創建,比如控制台通過 new HostBuilder() 形式啟動,具體參見官方文檔。

  為了更直觀的展示相關之間的關係,這裡我畫了個類圖來分解相關的職責,同時也是後邊具體實現的主要內容:

 二.  封裝實現

  從上邊類圖可以看出當前基礎框架主要由 BaseJobTrigger(觸發器基類),IJobExcutor(任務執行者介面),ListJobExcutor<IType>(通用列表迴圈任務執行者基類)。下邊分別就上邊三者貼出具體實現。

  1.  BaseJobTrigger(觸發器基類),實現代碼如下:

public abstract class BaseJobTrigger
       : IHostedService, IDisposable
{
    private Timer _timer;
    private readonly TimeSpan _dueTime;
    private readonly TimeSpan _periodTime;

    private readonly IJobExecutor _jobExcutor;

    /// <summary>
    /// 構造函數
    /// </summary>
    /// <param name="dueTime">到期執行時間</param>
    /// <param name="periodTime">間隔時間</param>
    /// <param name="jobExcutor">任務執行者</param>
    protected BaseJobTrigger(TimeSpan dueTime, 
         TimeSpan periodTime,
         IJobExecutor jobExcutor)
    {
        _dueTime = dueTime;
        _periodTime = periodTime;
        _jobExcutor = jobExcutor;
    }

    #region  計時器相關方法

    private void StartTimerTrigger()
    {
        if (_timer == null)
            _timer = new Timer(ExcuteJob,_jobExcutor,_dueTime, _periodTime);
        else
            _timer.Change(_dueTime, _periodTime);
    }

    private void StopTimerTrigger()
    {
        _timer?.Change(Timeout.Infinite, Timeout.Infinite);
    }

    private void ExcuteJob(object obj)
    {
        try
        {
            var excutor = obj as IJobExecutor;
            excutor?.StartJob();
        }
        catch (Exception e)
        {
           LogUtil.Error($"執行任務({nameof(GetType)})時出錯,信息:{e}");
        }
    }
    #endregion

    /// <summary>
    ///  系統級任務執行啟動
    /// </summary>
    /// <returns></returns>
    public virtual Task StartAsync(CancellationToken cancellationToken)
    {
        try
        {
            StartTimerTrigger();
        }
        catch (Exception e)
        {
            LogUtil.Error($"啟動定時任務({nameof(GetType)})時出錯,信息:{e}");
        }
        return Task.CompletedTask;
    }

    /// <summary>
    ///  系統級任務執行關閉
    /// </summary>
    /// <returns></returns>
    public virtual Task StopAsync(CancellationToken cancellationToken)
    {
        try
        {
           _jobExcutor.StopJob();
           StopTimerTrigger();
        }
        catch (Exception e)
        {
            LogUtil.Error($"停止定時任務({nameof(GetType)})時出錯,信息:{e}");
        }
        return Task.CompletedTask;
    }

    public void Dispose()
    {
        _timer?.Dispose();
    }
}           

  這個主要是完成對定時器的封裝,StartAsync和StopAsync 為 IHostService 系統服務介面,表示托管服務的開始和結束。

   2. IJobExcutor(任務執行者介面)

public interface IJobExecutor
{
    /// <summary>
    /// 開始任務
    /// </summary>
    void StartJob();

    /// <summary>
    ///  結束任務
    /// </summary>
    void StopJob();
}

  3. ListJobExcutor<IType>(通用列表迴圈任務執行者基類)

public abstract class ListJobExcutor<IType> 
                : IJobExecutor { /// <summary> /// 運行狀態 /// </summary> public bool IsRuning { get;protected set; }
/// <summary> /// 開始任務 /// </summary> public void StartJob() { // 任務依然在執行中,不需要再次喚起 if (IsRuning) return; IsRuning = true; IList<IType> list = null; // 結清實體list do { for (var i = 0; IsRuning && i < list?.Count;i++) { ExcuteItem(list[i],i); } list = GetExcuteSource(); } while (IsRuning && list?.Count > 0); IsRuning = false; }
public void StopJob() { IsRuning = false; } /// <summary> /// 獲取list數據源 /// </summary> /// <returns></returns> protected virtual IList<IType> GetExcuteSource() { return null; } /// <summary> /// 個體任務執行 /// </summary> /// <param name="item">單個實體</param> /// <param name="index">在數據源中的索引</param> protected virtual void ExcuteItem(IType item,int index) { } }

  這個是通用列表迴圈執的基礎封裝,因為業務中需要定時處理的大多是需要從資料庫或文件批量獲取數據,執行處理,例如到期提醒,定時清理超時訂單等場景。

  其主要功能實現是 從 GetExcuteSource() 獲取執行數據源,迴圈並通過 ExcuteItem() 執行個體任務,直到沒有數據源返回,則此次任務執行結束,等待下次任務觸發。如果當次執行時間過長,超過計時器時間間隔,重覆觸發時 當前任務還在進行中,則不做任何處理。如果數據量過大需要併發執行,子類可以在  ExcuteItem 中非同步處理。這樣既可保證併發順序執行。

 

 三. 示例演示

  以上三個元素就構成了當前定時任務的主要基礎框架,在實際處理一個任務的過程中,我們需要定義一個執行者(XXXJobExcutor),一個觸發器(XXXJobTrigger,構造函數傳入觸發時間,間隔,執行者)即可。這裡用兩個示例來做演示

  1. 基礎任務處理

public class TestJobTrigger:BaseJobTrigger
{
    public TestJobTrigger() : 
        base(TimeSpan.Zero,
            TimeSpan.FromMinutes(10),
            new TestJobExcutor())
    {
    }
}
public class TestJobExcutor 
                 : IJobExecutor
{
    public void StartJob()
    {
        LogUtil.Info("執行任務!");
    }

    public void StopJob()
    {
        LogUtil.Info("系統終止任務");
    }
}

  以上實現了TestJobTrigger 做任務觸發器,十分鐘執行一次。TestJobExcutor 作為具體執行者,做任務處理。啟動時只需在Startup.cs 中的ConfigureServices方法中添加如下代碼即可:

services.AddHostedService<TestJobTrigger>();

   2.  列表迴圈處理

public class ListJobTrigger 
    : BaseJobTrigger
{
    public ListJobTrigger() :
        base(TimeSpan.Zero,
            TimeSpan.FromMinutes(10),
            new ListJobExcutor())
    {

    }
}

public class ListJobExcutor 
    : ListJobExcutor<string>
{
    private int _page = 0;

    protected override IList<string> GetExcuteSource()
    {
        if (_page==0)
        {
            _page++;
            return new List<string>{ "1", "2", "3" };
        }
        return null;
    }

    protected override void ExcuteItem(string item, int index)
    {
        LogUtil.Info(item);
    }
}

  這個示例定時獲取字元串列表,並列印。一樣在Startup中註冊即可。    

 四.   註意事項

  1. 關於何時使用定時任務的問題

   之所以要說這個問題,是因為我看過不少同學把定時任務這種方式當成萬能膠,哪裡有縫往哪貼,一個不行起兩個。其實有很多場景都可以通過其關聯事件加消息隊列來完成,比如發簡訊,接收發送請求後塞消息隊列並返回請求方接收成功,隊列消費者來負責和簡訊服務商介面交互。只有對一些對時間屬性有要求的處理,咱們通過定時任務等處理,如.....會員生日提醒....

  2. 關於框架元素在解決方案的引用放置

  一個建議: IJobExcutor,ListJobExcutor<IType> 可以放置在通用類庫中,BaseJobTrigger,因為其依賴IHostService 放置在站點目錄下比較合適。

  3. 關於GenericHost的生存周期問題

  如果你使用的是控制台啟動,則此問題暫時可以忽略。

  如果你使用的是站點項目,並且還是通過IIS啟動,那麼你可能要註意了,因為.net core 的站點自身是有HOST宿主處理,IIS是其上代理,其啟動關閉,埠映射等由IIS內部完成。所以其依然受限於IIS的閑置回收影響,當IIS閑置回收時,其後的.Net Host也會被一同關閉,需要有新的請求進來時才會再次啟動。不過鑒於當前任務處理已經如此簡單,有個取巧的做法,實現一個站點自身的心跳檢測任務,IIS預設20分鐘回收,任務時間可以設為15分鐘(你也可以設置IIS站點回收時間),當然如果你的任務如果沒有那麼嚴格的時間要求你也可以不用處理,因為回收後一旦接受到新的請求,任務會再次發起。

 

  如果你已經看到這裡,並且感覺還行的話可以在下方點個贊,或者也可以關註我的公總號(見二維碼)

 

_________________________________________


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

-Advertisement-
Play Games
更多相關文章
  • 1.管道 進程間通信(IPC)方式二:管道(不推薦使用,瞭解即可),埠易導致數據不安全的情況出現。 2.共用數據 進程之間數據共用的模塊之一Manager模塊(少用): 進程間數據是獨立的,可以藉助於隊列或管道實現通信,二者都是基於消息傳遞的雖然進程間數據獨立,但可以通過Manager實現數據共用 ...
  • 一、input()函數 在 Python 中,使用內置函數 input()可以接收用戶的鍵盤輸入。 input()函數的基本用法如 下: 其中,variable 為保存輸入結果的變數,雙引號內的文字用於提示要輸入的內容。 二、print()函數預設的情況下,在Python中,使用內置的print() ...
  • 一:EL表達式 1.概述:在jsp開發中,為了獲取Servlet域對象中存儲的數據,經常要寫很多java代碼,這樣的做法會使JSP頁面混亂,難以維護,為此,在JSP2.0規範中提供了EL表達式。它是Expression Language的縮寫。 2.語法:${表達式} 2.1內置對象: 2.1.1獲 ...
  • 一、引言 官網文檔:http://www.mybatis.org/generator/index.html 通過使用官方提供的mapper自動生成工具,mybatis-generator-core-1.3.2來自動生成po類和mapper映射文件。 作用:mybatis官方提供逆向工程,可以使用它通 ...
  • 1. 什麼是列表 定義: 能裝對象的對象 在python中使用 [] 來描述列表, 內部元素用逗號隔開. 對數據類型沒有要求 列表存在索引和切片. 和字元串是一樣的. 2. 相關的增刪改查操作 添加: 1. append() 追加 2. insert(位置, 元素) 插入指定元素到指定位置 刪除: ...
  • 題意 "題目鏈接" Sol 神仙題Orzzzz 題目可以轉化為從$\leqslant M$的質數中選出$N$個$xor$和為$0$的方案數 這樣就好做多了 設$f(x) = [x \text{是質數}]$ $n$次異或FWT即可 快速冪優化一下,中間不用IFWT,最後轉一次就行(~~然而並不知道為什 ...
  • 1.進程同步/串列(鎖) 進程之間數據不共用,但共用同一套文件系統,所以訪問同一個文件,或同一個列印終端,沒有問題,但共用帶來的是競爭容易錯亂,如搶票時。這就需讓進程一個個的進去保證數據安全,也就是加鎖處理,Lock 併發,效率高,但是競爭同一個文件時,導致數據混亂 加鎖,由併發改成了串列,犧牲了運 ...
  • 最後來看看前面一直說的 Engine(工作引擎) ,工作引擎介面是 在`ServiceProvider IServiceProviderEngine`介面和其實現類的整體結構 IServiceProviderEngine類型繼承關係 繼承了 介面,也就是說工作引擎也具有 GetService() 方 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...