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集成

后台工作管理者設計成了可以被其他的后台工作管理者取代。查看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應用是否開啟並且處於運行狀態。Hangfire文檔講解了一些其他的方法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM