ABP框架系列之十四:(Background-Jobs-And-Workers-背景工作和工人)


Introduction

ASP.NET Boilerplate provides background jobs and workers those are used to execute some tasks in background threads in an application.

ASP.NET樣板提供背景工作和工人是用來執行一些任務在后台線程中的一個應用。

Background Jobs

Background jobs are used to queue some tasks to be executed in background in a queued and persistent manner. You may need background jobs for several reasons. Some examples:

  • To perform long-running tasks to not wait users. For example; A user presses a 'report' button to start a long running reporting job. You add this job to the queue and send report result to your user via email when it's completed.
  • To create re-trying and persistent tasks to guarantee a code will be successfully executed. For example; You can send emails in a background job to overcome temporary failures and guarantie that it's eventually will be sent. Also, thus, users do not wait while sending emails.
  • 后台作業用於在隊列中以隊列和持久的方式在后台執行某些任務。您可能需要后台工作有以下幾個原因。一些例子:

    執行長時間任務而不是等待用戶。例如,用戶按下“報告”按鈕開始一個長時間運行的報表作業。將此作業添加到隊列中,並在完成時將報告結果通過電子郵件發送給用戶。
    要創建重新嘗試和持久的任務,確保代碼將成功執行。例如;
    你可以發送電子郵件,在后台工作克服暫時的失敗和保證它最終將被發送。因此,用戶在發送電子郵件時不會等待。

About Job Persistence

See Background Job Store section for more information on job persistence.

有關工作持久性的更多信息,請參見后台作業存儲部分。

Create a Background Job

We can create a background job class by either inheriting from BackgroundJob<TArgs> class or directly implementing IBackgroundJob<TArgs> interface.

我們可以通過繼承backgroundjob <tags>類或直接實現ibackgroundjob <tags>接口創建一個后台作業類。

Here is the most simple background job:

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

A background job defines an Execute method gets an input argument. Argument type is defined as generic class parameter as shown in the example.

A background job must be registered to dependency injection. Implementing ITransientDependency is the simplest way.

Lest's define a more realistic job which sends emails in a background queue:

后台作業定義了執行方法獲取輸入參數。參數類型被定義為泛型類參數,如示例中所示。

后台作業必須注冊到依賴注入。實施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;
    }
    
    [UnitOfWork]
    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);
    }
}

We injected user repository (to get user emails) and email sender (a service to send emails) and simply sent the email. SimpleSendEmailJobArgs is the job argument here and defined as shown below:

我們給用戶注入了存儲庫(獲取用戶電子郵件)和發送電子郵件(發送電子郵件的服務),然后簡單地發送電子郵件。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; }
}

A job argument should be serializable, because it's serialized and stored in the database. While ASP.NET Boilerplate default background job manager uses JSON serialization (which does not need [Serializable] attribute), it's better to define [Serializable] attribute since we may switch to another job manager in the future, which may use .NET's built-in binary serialization.

Keep your arguments simple (like DTOs), do not include entities or other non serializable objects. As shown in the SimpleSendEmailJob sample, we can only store Id of an entity and get the entity from repository inside the job.

工作應該是可序列化的,因為它是序列化並存儲在數據庫中。而ASP.NET樣板默認背景工作經理使用JSON序列化(不需要序列化屬性[ ]),它的更好的定義[ ]屬性序列化因為我們可以切換到另一個工作的經理在未來,可以使用.NET內置的二進制序列化。

保持你的論點簡單(像DTOs),不包括單位或其他非序列化的對象。在simplesendemailjob示例所示,我們可以只存儲ID的實體和從工作中得到的實體庫。

Add a New Job To the Queue

After defining a background job, we can inject and use IBackgroundJobManager to add a job to the queue. See a sample for TestJob defined above:

在定義一個背景的工作,我們可以注射和使用ibackgroundjobmanager添加任務到隊列。看到一個testjob上面定義的樣本:

public class MyService
{
    private readonly IBackgroundJobManager _backgroundJobManager;

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

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

We sent 42 as argument while enqueuing. IBackgroundJobManager will instantiate and execute the TestJob with 42 as argument.

Let's see to add a new job for SimpleSendEmailJob defined above:

[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 (or EnqueueAsync) method has other parameters such as priority and delay.

Enqueu(或enqueueasync)方法具有優先級和時延等參數。

Default Background Job Manager

IBackgroundJobManager is implemented by BackgroundJobManager default. It can be replaced by another background job provider (see hangfire integration). Some information on default BackgroundJobManager:

  • It's a simple job queue works as FIFO in a single thread. It uses IBackgroundJobStore to persist jobs (see next section).
  • It retries job execution until job successfully runs (does not throw any exception but logs them) or timeouts. Default timeout is 2 days for a job.
  • It deletes a job from store (database) when it's successfully executed. If it's timed out, sets as abandoned and leaves on the database.
  • It increasingly waits between retries for a job. Waits 1 minute for first retry, 2 minutes for second retry, 4 minutes for third retry and so on..
  • It polls the store for jobs in fixed intervals. Queries jobs ordering by priority (asc) then by try count (asc).
  • ibackgroundjobmanager由backgroundjobmanager默認實現。它可以通過一個后台作業提供商所取代(見遲發性整合)。一些默認的backgroundjobmanager信息:

    這是一個簡單的工作隊列,就像單線程中的FIFO一樣工作。它采用ibackgroundjobstore堅持工作(見下一節)。
    在重試作業執行直到工作成功運行(不拋出任何異常但日志他們)或超時。默認超時為工作2天。
    當它成功執行時,它會從存儲(數據庫)中刪除一個作業。如果超時,則設置為放棄並離開數據庫。
    它越來越多地等待重試之間找工作。等待第一次重試1分鍾,第二次重試2分鍾,4分鍾重試第三分鍾等等。
    它以固定的間隔輪詢商店的工作。查詢按優先級排序的作業(ASC),然后通過嘗試計數(ASC)。

Background Job Store

Default BackgroundJobManager needs a data store to save and get jobs. If you do not implement IBackgroundJobStore then it uses InMemoryBackgroundJobStore which does not save jobs in a persistent database. You can simply implement it to store jobs in a database or you can use module-zero which already implements it.

If you are using a 3rd party job manager (like Hanfgire), no need to implement IBackgroundJobStore.

默認backgroundjobmanager需要數據存儲和獲得工作。如果不實施ibackgroundjobstore然后用inmemorybackgroundjobstore不保存在數據庫的工作。您可以簡單地實現它來在數據庫中存儲作業,也可以使用已經實現它的模塊零。

如果您使用的是第三方管理(像Hanfgire),不需要實現ibackgroundjobstore。

Configuration

You can use Configuration.BackgroundJobs in PreInitialize method of your module to configure background job system.

你可以使用configuration.backgroundjobs在分發配置后台作業系統模塊的方法。

Disabling Job Execution(禁止任務執行

You may want to disable background job execution for your application:

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

    //...
}

This is rarely needed. But, think that you are running multiple instances of your application working on the same database (in a web farm). In this case, each application will query same database for jobs and executes them. This leads multiple execution of same jobs and some other problems. To prevent it, you have two options:

  • You can enable job execution for only one instance of the application.
  • You can disable job execution for all instances of the web application and create a seperated standalone application (example: a Windows Service) that executes background jobs.
  • 這是很少需要的。但是,請考慮您正在運行多個應用程序在同一數據庫上工作的實例(在Web站點中)。在這種情況下,每個應用程序將查詢作業的同一數據庫並執行它們。這將導致多個相同的作業和一些其他問題的執行。為了防止它,你有兩個選擇:

    您可以僅為應用程序的一個實例啟用作業執行。
    您可以禁用Web應用程序所有實例的作業執行,並創建獨立的獨立應用程序(例如:Windows服務),執行后台作業。

Exception Handling(異常處理

Since default background job manager should re-try failed jobs, it handles (and logs) all exceptions. In case you want to be informed when an exception occurred, you can create an event handler to handleAbpHandledExceptionData. Background manager triggers this event with a BackgroundJobException exception object which wraps the real exception (get InnerException for the actual exception).

由於默認后台作業管理器應該重新嘗試失敗的作業,它會處理(並記錄)所有異常。如果你想要了解異常發生時,你可以創建一個事件處理程序handleabphandledexceptiondata。觸發此事件背景的經理與backgroundjobexception異常對象將真正的例外(得到的實際異常InnerException)。

Hangfire Integration(遲發性整合

Background job manager is designed as replaceable by another background job manager. See hangfire integration document to replace it by Hangfire .

后台作業管理器被另一個后台任務管理器設計為可替換。看到遲發集成文檔來取代它的遲發。

Background Workers

Background workers are different than background jobs. They are simple independent threads in the application running in the background. Generally, they run periodically to perform some tasks. Examples;

  • A background worker can run periodically to delete old logs.
  • A background worker can periodically to determine inactive users and send emails to return back to your application.
  • 背景工作人員不同於背景工作。它們是運行在后台的應用程序中的簡單獨立線程。通常,它們周期性地運行以執行某些任務。實例;

    后台工作者可以定期運行以刪除舊日志。
    后台工作者可以周期性地決定非活動用戶並發送電子郵件返回到應用程序。

Create a Background Worker

To create a background worker, we should implement IBackgroundWorker interface. Alternatively, we can inherit from BackgroundWorkerBase or PeriodicBackgroundWorkerBase based on our needs.

Assume that we want to make a user passive, if he did not login to the application in last 30 days. See the code:

創建一個背景的工人,我們應該實施ibackgroundworker接口。或者,我們可以從backgroundworkerbase或根據我們的需要periodicbackgroundworkerbase繼承。

假設我們想讓用戶被動,如果他沒有登錄到應用程序在過去30天。見代碼:

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();
        }
    }
}

This is a real code and directly works in ASP.NET Boilerplate with module-zero.

  • If you derive from PeriodicBackgroundWorkerBase (as in this sample), you should implement DoWork method to perform your periodic working code.
  • If you derive from BackgroundWorkerBase or directly implement IBackgroundWorker, you will override/implement Start, Stop and WaitToStop methods. Start and Stop methods should be non-blocking, WaitToStop method should wait worker to finish it's current critical job.
  • 這是一個真正的代碼,直接在ASP.NET工作的樣板模塊零。

    如果你從periodicbackgroundworkerbase(就像這個例子),你應該實現DoWork方法執行你的周期運行代碼。
    如果你從backgroundworkerbase或直接實施IBackgroundWorker獲得,你將覆蓋/實現啟動、停止和waittostop方法。啟動和停止的方法應該是非阻塞的,WaitToStop的方法應該等待工人來完成它的當前重要的工作。

Register Background Workers

After creating a background worker, we should add it to IBackgroundWorkerManager. Most common place is the PostInitialize method of your module:

在創建背景的工人,我們應該將它添加到ibackgroundworkermanager。最常見的地方是你的模塊的postinitialize方法:

public class MyProjectWebModule : AbpModule
{
    //...

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

While we generally add workers in PostInitialize, there is no restriction on that. You can inject IBackgroundWorkerManager anywhere and add workers on runtime. IBackgroundWorkerManager will stop and release all registered workers when your applications is being shutdown.

而我們通常添加在PostInitialize的工人,有沒有限制。你可以把ibackgroundworkermanager隨時隨地在運行時添加的工人。ibackgroundworkermanager將停止並釋放所有注冊工人當應用程序被關閉。

Background Worker Lifestyles

Background workers are generally implemented as singleton. But there is no restriction. If you need multiple instance of same worker class, you can make it transient and add more than one instance to IBackgroundWorkerManager. In this case, your worker probably will be parametric (say that you have a single LogCleaner class but two LogCleaner worker instances they watch and clear different log folders).

后台工作人員通常是作為單例實現的。但沒有限制。如果你需要同工人階級的多個實例,你可以使它的瞬態和添加多個實例ibackgroundworkermanager。在這種情況下,你的員工可能將參數(說你有一個單一的logcleaner類但兩logcleaner工人情況下他們看清楚不同的日志文件夾)。

Advanced Scheduling(先進的調度

ASP.NET Boilerplate's background worker system are simple. It has not a schedule system, except periodic running workers as demonstrated above. If you need to more advanced scheduling features, we suggest you to checkQuartz or another library.

ASP.NET樣板的背景工人系統簡單。它沒有時間表系統,除了定期運行工人,如上面所示。如果你需要更先進的調度功能,我們建議您checkquartz或另一個圖書館。

Making Your Application Always Running

Background jobs and workers only works if your application is running. An ASP.NET application shutdowns by default if no request is performed to the web application for a long time. So, if you host background job execution in your web application (this is the default behaviour), you should ensure that your web application is configured to always running. Otherwise, background jobs only works while your applications is in use.

There are some techniques to accomplish that. Most simple way is to make periodic requests to your web application from an external application. Thus, you can also check if your web application is up and running. Hangfire documentation explains some other ways.

后台應用程序和工作只有在應用程序運行時才工作。ASP.NET應用程序關閉默認如果沒有要求進行長期的Web應用。因此,如果在Web應用程序中托管后台任務執行(這是默認行為),則應確保Web應用程序配置為始終運行。否則,后台作業只在應用程序使用時才有效。

有一些技術可以做到這一點。最簡單的方法是從外部應用程序向Web應用程序發出周期性請求。因此,您還可以檢查Web應用程序是否已啟動和運行。遲發性文檔解釋了一些其他的方法。


免責聲明!

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



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