1. 程式人生 > >ABP框架理論學習之後臺工作(Jobs)和後臺工作者(Workers)

ABP框架理論學習之後臺工作(Jobs)和後臺工作者(Workers)

返回總目錄

本篇目錄

介紹

ABP提供了後臺工作和後臺工作者,它們會在應用程式的後臺執行緒中執行一些任務。

後臺工作

後臺工作以佇列和持續的方式在後臺給一些即將被執行的任務排隊。你可能因為某些原因需要後臺工作,比如:

  • 執行長時間執行的任務。比如,一個使用者按了“report”按鈕來啟動一個長時間執行的報告工作,點選了這個按鈕你不可能讓使用者一直處於等待狀態,所以你應該將這個工作(job)新增到 佇列(queue)中,然後,當這項工作完成時,通過郵件將報告結果傳送給該使用者。
  • 建立重複嘗試(re-trying)和持續的任務來保證程式碼將會 成功執行。比如,你可以在後臺工作中傳送郵件以克服 臨時失敗
    ,並 保證郵件最終能夠傳送出去。因此,當傳送郵件的時候使用者不需要等待。

建立一個後臺工作

我們可以通過繼承BackgroundJob類或者直接實現 IBackgroundJob介面來建立一個後臺工作類。

下面是一個最簡單的後臺工作:

public class TestJob : BackgroundJob<int>, ITransientDependency
{
    public override void Execute(int number)
    {
        Logger.Debug(number.ToString());
    }
}

該後臺工作定義了一個需要輸入引數的Execute

方法。引數型別是泛型引數型別。

後臺工作必須註冊到依賴注入系統中,實現ITransientDependency是最簡單的方式。

接下來定義一個更現實的工作,它會在後臺佇列中傳送郵件:

public class SimpleSendEmailJob : BackgroundJob<SimpleSendEmailJobArgs>, ITransientDependency
{
    private readonly IRepository<User, long> _userRepository;
    private readonly IEmailSender _emailSender;

    public SimpleSendEmailJob(IRepository<User, long> userRepository, IEmailSender emailSender)
    {
        _userRepository = userRepository;
        _emailSender = emailSender;
    }

    public override void Execute(SimpleSendEmailJobArgs args)
    {
        var senderUser = _userRepository.Get(args.SenderUserId);
        var targetUser = _userRepository.Get(args.TargetUserId);

        _emailSender.Send(senderUser.EmailAddress, targetUser.EmailAddress, args.Subject, args.Body);
    }
}

我們注入了user倉儲(為了獲得使用者資訊)和email傳送者(傳送郵件的服務),然後簡單地傳送了該郵件。SimpleSendEmailJobArgs是該工作的引數,它定義如下:

[Serializable]
public class SimpleSendEmailJobArgs
{
    public long SenderUserId { get; set; }

    public long TargetUserId { get; set; }

    public string Subject { get; set; }

    public string Body { get; set; }
}

工作引數應該是serializable(可序列化),因為要將它 序列化並存儲到資料庫中。雖然ABP預設的後臺工作管理者使用了JSON序列化(它不需要[Serializable]特性),但是最好定義 [Serializable]特性,因為我們將來可能會轉換到其他使用二進位制序列化的工作管理者。

記住,要保持你的引數簡單,不要在引數中包含實體或者其他非可序列化的物件。正如上面的例子演示的那樣,我們只儲存了實體的 Id,然後在該工作的內部從倉儲中獲得該實體。

新增新工作到佇列

當定義了一個後臺工作後,我們就可以注入並使用IBackgroundJobManager來新增一個工作到佇列中。看上面定義的TestJob的例子:

public class MyService
{
    private readonly IBackgroundJobManager _backgroundJobManager;

    public MyService(IBackgroundJobManager backgroundJobManager)
    {
        _backgroundJobManager = backgroundJobManager;
    }

    public void Test()
    {
        _backgroundJobManager.Enqueue<TestJob, int>(42);
    }
}

當入隊(Enqueue)時,我們將42作為引數傳遞。IBackgroundJobManager將會例項化並使用42作為引數執行TestJob。

讓我們看一下如何為上面定義的SimpleSendEmailJob新增一個新的工作:

[AbpAuthorize]
public class MyEmailAppService : ApplicationService, IMyEmailAppService
{
    private readonly IBackgroundJobManager _backgroundJobManager;

    public MyEmailAppService(IBackgroundJobManager backgroundJobManager)
    {
        _backgroundJobManager = backgroundJobManager;
    }

    public async Task SendEmail(SendEmailInput input)
    {
            await _backgroundJobManager.EnqueueAsync<SimpleSendEmailJob, SimpleSendEmailJobArgs>(
            new SimpleSendEmailJobArgs
            {
                Subject = input.Subject,
                Body = input.Body,
                SenderUserId = AbpSession.GetUserId(),
                TargetUserId = input.TargetUserId
            });
    }
}

Enqueu (或 EnqueueAsync)方法還有其他的引數,比如 priority和 delay(優先順序和延遲)

預設的後臺工作管理者

IBackgroundJobManager預設是由BackgroundJobManager實現的。它可以被其他的後臺工作提供者替代(看後面的Hangfire整合)。關於預設的BackgroundJobManager一些資訊如下:

  • 它是一個在單執行緒中以FIFO(First In First Out)工作的簡單工作佇列,使用 IBackgroundJobStore來持久化工作。
  • 它會重複嘗試執行工作,直到工作成功執行(不會丟擲任何異常)或者超時。預設的超時是一個工作2天。
  • 當成功執行後,它會從儲存(資料庫)中刪除該工作。如果超時了,就會將該工作設定為 abandoned(廢棄的),並保留在資料庫中。
  • 在重複嘗試一個工作之間會增加等待時間。第一次重試時等待1分鐘,第二次等待2分鐘,第三次等待4分鐘等等。
  • 在固定的時間間隔輪詢工作的儲存。查詢工作時先按優先順序排序,再按嘗試次數排序。

後臺工作儲存
預設的BackgroundJobManager需要一個數據儲存來儲存、獲得工作。如果你沒有實現IBackgroundJobStore,那麼它會使用 InMemoryBackgroundJobStore,它不會將工作持久化到資料庫中。你可以簡單地實現它來儲存工作到資料庫或者你可以使用module-zero,它已經實現了IBackgroundJobStore。

如果你正在使用第三方的工作管理者(像Hangfire),那麼不需要實現IBackgroundJobStore。

配置

你可以在模組的PreInitialize方法中使用Configuration.BackgroundJobs來配置後臺工作系統。

關閉工作執行功能
你可能想關閉應用程式的後臺工作執行:

public class MyProjectWebModule : AbpModule
{
    public override void PreInitialize()
    {
        Configuration.BackgroundJobs.IsJobExecutionEnabled = false;
    }

    //...
}

這種情況很罕見,但是想一下,如果你正在對相同的資料庫執行多個應用的例項(在web應用中)。在這種情況下,每個應用都會為工作查詢相同的資料庫並執行它們。這會導致相同工作的多次執行和一些其它問題。要阻止這種情況發生,你有兩種選擇:

  • 你可以只為該應用的一個例項開啟工作執行。
  • 你可以為該web應用的所有例項關閉工作執行,然後建立一個會執行後臺工作的獨立應用(比如一個Windows服務)。

Hangfire整合

後臺工作管理者設計成了可以被其他的後臺工作管理者取代。檢視來替代預設的後臺工作管理者。

後臺工作者

後臺工作者不同於後臺工作。它們是執行在應用後臺的簡單獨立執行緒。一般來說,它們會定期地執行一些任務。比如:

  • 後臺工作者可以定期執行來刪除舊的日誌
  • 後臺工作者可以定期執行來確定不活躍的使用者,並給他們傳送郵件以使他們返回你的應用。

建立後臺工作者

要建立後臺工作者,我們應該實現IBackgroundWorker介面。除此之外,我們可以基於需求從 BackgroundWorkerBase或者 PeriodicBackgroundWorkerBase繼承。

假設一個使用者在過去30天內沒有登入到該應用,那我們想要讓Ta的狀態為passive。看下面的程式碼:

public class MakeInactiveUsersPassiveWorker : PeriodicBackgroundWorkerBase, ISingletonDependency
{
    private readonly IRepository<User, long> _userRepository;

    public MakeInactiveUsersPassiveWorker(AbpTimer timer, IRepository<User, long> userRepository)
        : base(timer)
    {
        _userRepository = userRepository;
        Timer.Period = 5000; //5 seconds (good for tests, but normally will be more)
    }

    [UnitOfWork]
    protected override void DoWork()
    {
        using (CurrentUnitOfWork.DisableFilter(AbpDataFilters.MayHaveTenant))
        {
            var oneMonthAgo = Clock.Now.Subtract(TimeSpan.FromDays(30));

            var inactiveUsers = _userRepository.GetAllList(u =>
                u.IsActive &&
                ((u.LastLoginTime < oneMonthAgo && u.LastLoginTime != null) || (u.CreationTime < oneMonthAgo && u.LastLoginTime == null))
                );

            foreach (var inactiveUser in inactiveUsers)
            {
                inactiveUser.IsActive = false;
                Logger.Info(inactiveUser + " made passive since he/she did not login in last 30 days.");
            }

            CurrentUnitOfWork.SaveChanges();
        }
    }
}

這是現實中的程式碼,並且在具有module-zero模組的ABP中直接有效。

  • 如果你從PeriodicBackgroundWorkerBase 類繼承(如這個例子),那麼你應該實現 DoWork方法來執行定期執行的程式碼。
  • 如果從BackgroundWorkerBase繼承或直接實現了 IBackgroundWorker,那麼你要重寫或者實現Start, Stop 和 WaitToStop方法。Start和Stop方法應該是 非阻塞的(non-blocking),WaitToStop方法應該等待工作者完成當前重要的工作。

註冊後臺工作者

建立一個後臺工作者後,我們應該把它新增到IBackgroundWorkerManager,通常放在模組的PostInitialize方法中:

public class MyProjectWebModule : AbpModule
{
    //...

    public override void PostInitialize()
    {
        var workManager = IocManager.Resolve<IBackgroundWorkerManager>();
        workManager.Add(IocManager.Resolve<MakeInactiveUsersPassiveWorker>());
    }
}

雖然一般我們將工作者新增到PostInitialize方法中,但是沒有強制要求。你可以在任何地方注入IBackgroundWorkerManager,在執行時新增工作者。
當應用要關閉時,IBackgroundWorkerManager會停止並釋放所有註冊的工作者。

後臺工作者生命週期

後臺工作者一般實現為單例的,但是沒有嚴格限制。如果你需要相同工作者類的多個例項,那麼可以使它成為transient(每次使用時建立),然後給IBackgroundWorkerManager新增多個例項。在這種情況下,你的工作者很可能會引數化(比如,你有單個LogCleaner類,但是兩個LogCleaner工作者例項會監視並清除不同的log資料夾)。

讓你的應用程式一直執行

只有當你的應用執行時,後臺工作和工作者才會工作。如果一個Asp.Net 應用長時間沒有執行請求,那麼它預設會關閉(shutdown)。如果你想讓後臺工作一直在web應用中執行(這是預設行為),那麼你要確保web應用配置成了總是執行。否則,後臺工作只有在應用使用時才會執行。

有很多技術來實現這個目的。最簡單的方法是從外部應用定期向你的web應用傳送請求。這樣,你可以檢查web應用是否開啟並且處於執行狀態。講解了一些其他的方法。

相關推薦

ABP框架理論學習後臺工作(Jobs)後臺工作者Workers

返回總目錄 本篇目錄 介紹 ABP提供了後臺工作和後臺工作者,它們會在應用程式的後臺執行緒中執行一些任務。 後臺工作 後臺工作以佇列和持續的方式在後臺給一些即將被執行的任務排隊。你可能因為某些原因需要後臺工作,比如: 執行長時間執行的任務。比如,一個使用者按了“report”按鈕來啟動一個長時間執行的報

【Oracle 叢集】ORACLE DATABASE 11G RAC 知識圖文詳細教程RAC 工作原理相關元件

概述:寫下本文件的初衷和動力,來源於上篇的《oracle基本操作手冊》。oracle基本操作手冊是作者研一假期對oracle基礎知識學習的彙總。然後形成體系的總結,一則進行回顧複習,另則便於查詢使用。本圖文文件亦源於此。閱讀Oracle RAC安裝與使用教程前,筆者先對這篇文章整體構思和形成進行梳理。

C#高階知識點&(ABP框架理論學習高階篇)——白金版

前言摘要 很早以前就有要寫ABP高階系列教程的計劃了,但是遲遲到現在這個高階理論系列才和大家見面。其實這篇部落格很早就著手寫了,只是樓主一直寫寫停停。看看下圖,就知道這篇部落格的生產日期了,誰知它的出廠日期竟是現在,2個半月了,哎,也是醞釀夠久的了! 期間,很多園友一直在催我更新,都是在期待我的這個ABP高

小白學習路,網絡編程

多人 困難 get err lose imp 出現 popen one 一,socket進階 在前面的博客中講到了一些基本的計算機網絡知識,有一點也是為我在要考傳輸與交換看到一個題,然後就看到說ARP屬於網絡層,因為ARP協議跟網絡相關,但是我前面的博客說的是ARP協議屬於

Pthon學習路 第四篇 Python基礎

pri bsp programs -s alt 如果 lex class 算數運算 1.運算符:+ - *(乘法) /(除法) %(求余) //(求商) **(求冪) 2.成員運算:in not in:判斷單個字符或者子序列在不在字符串中。(n

python的學習路===小白學編程3

都是 csv文件 size ffi business cal dict stock status 額不找接口,就是懈怠了。。。。 數據庫表弄好了,但是返回來寫又出問題了。。檢查了半點才發現是拼寫錯誤。。。。 混亂的思路一直沒有理清,所以建好的表結構發現也還是不能夠馬上用。。

Spring學習Spring三種裝配機制:顯示裝配bean

  今天我們介紹一下Spring三種裝配機制中的另外兩種裝配方式:JavaConfig和XML配置,這兩種方式區別於自動化裝配方式都屬於顯示裝配。 1、Java程式碼裝配bean 首先,我們通過在Config類中使用@Bean註解來宣告bean; @Bean註

Java學習路 第五章 面向物件1

面向物件(1) 1、認識物件 (1)萬物皆物件。 (2)物件=特點或特徵(屬性)+行為或(方法)。 (3)物件由屬性和方法組成,一定要具體到個體上。 2、認識類 (1)類是一些具有共同屬性和方法的物件的一個抽象。 (2)類是一個概念,不是具體的一個物件。 (3)

一起學Netty十八netty原始碼學習netty server端原始碼初讀

server端是使用了Reactor模式對nio進行了一些封裝,Reactor模式網上有很多資料,不贅述,瞭解了這個模式開始看原始碼 netty的版本是4.0.21.Final <dependency> <groupId>io.netty<

10、SSM框架-Spring AOP基於註解的宣告式AspectJ10

spring除了支援Schema方式配置AOP,還支援註解方式:使用@AspectJ風格的切面宣告。匯入需要的包:aspectjweaver.jar、aopalliance-1.0.jar 一、基本使用方法  1.1、啟用對@AspectJ的支援        Sprin

深度學習卷積神經網路程式設計實現

void conv_bprop(Layer *layer, Layer *prev_layer, bool *pconnection) { int index = 0; int size = prev_layer->map_w * prev_layer->map_h; // delta

qt學習qt介面控制小烏龜運動

PS:小編也是新手,在做介面時也走了很多彎路,很多博主寫的不是很明確,所以小編記錄了自己製作的全過程,希望對初學者有用,共同進步。 1.新建工作空間catkin_new,並建立src包,。 2.新建ROS workspace ,儲存在src資料夾內,開啟工程是直

Vue學習原始碼分析--Vue.js依賴收集

為什麼要依賴收集 先看下面這段程式碼 new Vue({ template: `<div> <span>text1:</span> {{text1}}

Netty netty原始碼學習netty server端原始碼初讀

上一篇簡單的分析了一下NioEventLoop,ServerBootstrap等元件的建立過程做的一些操作 現在我們一起看下當SingleThreadEventExecutor.java中的thread啟動後,netty做的一些最最重要的一些操作 我們接著昨天的程

ROS學習tf.3編寫一個tf接收器C++

ROS.tf3編寫一個tf接收器(C++)宣告:本教程來自於ROS.WIKI,本人在整理過程中可能出現一些錯誤和問題,有問題可以去檢視官網版本也可以諮詢本人在之前的教程中,我們建立了一個tf廣播公司向tf釋出一隻烏龜的姿勢。 在本教程中,我們將建立一個tf接收器來開始使用tf

UI“三重天”selenium--常用API問題處理

Selenium常用API: 前面兩篇示例程式碼中用到了一些selenium的API方法,例如定位元素的八種方法、訪問url、等待、操作瀏覽器、獲取title、點選、清理等等。 有關於selenium的常用API在園子中有寫的非常詳細的文章。先貼大佬文章地址:https://www.cnblogs.com

Java多執行緒Condition實現原理原始碼分析

章節概覽、 1、概述 上面的幾個章節我們基於lock(),unlock()方法為入口,深入分析了獨佔鎖的獲取和釋放。這個章節我們在此基礎上,進一步分析AQS是如何實現await,signal功能。其功能上和synchronize的wait,notify一樣。

iOS開發Weex爬坑路環境部署Devtools Debug

前言 不多說,直接開始Weex,算是記錄這段時間對新技術的學習積累。期間看了很多Vue.js和Node.js的基礎,順便捋了以下CSS的flex-box佈局等等前端的知識,太多了太雜了,還是用筆記記錄下。直接從官方介紹,開始 補充一個傳送門:和文章沒什麼關係 JS中的async和await

機房收費系統MDI主窗體子窗體覆蓋

    在機房收費系統中,frmMain作為MDI的主窗體,想要在MDI主窗體上新增控制元件,picture控制元件可以起到此作用,但是在添加了picture控制元件以後,問題也就隨之而來了。     點選子窗體的時候,子窗體被“吃掉”了,怎麼樣讓他顯示出來呢?想要解

【Oracle 叢集】ORACLE DATABASE 11G RAC 知識圖文詳細教程RAC 特殊問題實戰經驗

在需要將一個 LUN (邏輯單元號)對映給多個節點、為叢集提供一個共享的儲存卷時,同一個儲存 LUN 在各個主機端的 LUNID 必須是相同的。比如:  (一) 在為多個 ESX 節點建立一個 VMFS 卷的時候 (二) 在雙機 HA 叢集建立共享儲存的時候 時間一致性 叢集模式下,各