Base solution for your next web application
Open Closed

How to "fire and forget" a job or task. #9945


User avatar
0
timmackey created

Product Version 8.0.0 Product Type: Angular Framework: .NET Core 3.0 ABP Framework: 5.0

I'm currently using the BackgroundJob class successfully. Some jobs execute in few seconds. Other jobs execute in minutes or hours. Each job is independent, with no dependency on previously executed jobs. Jobs are initiated by user actions. Long running jobs cause fast running jobs to wait until the long running job has finished because BackgroundJob executes queued jobs sequentially. This causes one user's job to wait until all other jobs have run to completion. How can I implement "fire and forget" jobs (or Tasks) that execute in parallel? I want a user's job to begin execution immediately.

Below are code snippets of the current implementation:

    [AbpAuthorize(AppPermissions.Pages_Exams)]
    public class ExamsAppService : ngTTMAppServiceBase, IExamsAppService
    {
        .
        .
        .
        [AbpAuthorize(AppPermissions.Pages_Exams_Create)]
        private async Task<ServiceStatusDto> CreateExamJob(string examGuid)
        {
            ServiceStatusDto status = new ServiceStatusDto(L("Exams"), L("Success"));
            long UserId = (long)AbpSession.UserId;

            string workingDirectory = _webHostEnvironment.ContentRootPath;

            try
            {
                CreatJobArgs args =
                    new CreatJobArgs
                    {
                        tenantId = AbpSession.TenantId,
                        userId = UserId,
                        jobGuid = examGuid,
                        folder = StorageLibrary.Storage.FILES_FOLDER,
                        operation = JobOps.JOB_OP_CREATE,
                        workingDirectory = workingDirectory
                    };

                using (_abpSession.Use(AbpSession.TenantId, UserId))
                {
                    status.BackgroundJobId = await
                        _backgroundJobManager.EnqueueAsync<CreateExamJob, CreatJobArgs>(args);
                }
                status.Guid = examGuid;
                return status;
            }
            catch (Exception ex)
            {
                ExceptionUtility.LogException(UserId, ex);
                status.Success = false;
                status.FailureReason = ex.Message;
                return status;
            }
        }
    }

=============================================

    public class CreateExamJob : BackgroundJob<CreatJobArgs>, ITransientDependency, IApplicationService
    {
        private readonly ITempFileCacheManager              _tempFileCacheManager;
        private readonly IWebHostEnvironment                _hostingEnvironment;
        private readonly ISettingManager                    _settingManager;
        private readonly ILocalizationManager               _localizationManager;
        private readonly IAbpSession                        _abpSession;
        private readonly IBinaryObjectManager               _binaryObjectManager;
        private DateTime jobStartTime;

        public CreateExamJob(
            ITempFileCacheManager                   tempFileCacheManager,
            ISettingManager                         settingManager,
            IWebHostEnvironment                     hostingEnvironment,
            ILocalizationManager                    localizationManager,
            IBinaryObjectManager                    binaryObjectManager,
            IAbpSession                             abpSession
        )
        {
            _settingManager = settingManager;
            _hostingEnvironment = hostingEnvironment;
            _tempFileCacheManager = tempFileCacheManager;
            _localizationManager = localizationManager;
            _binaryObjectManager = binaryObjectManager;
            _abpSession = abpSession;
        }
        
        [UnitOfWork]
        public override void Execute(CreatJobArgs args)
        {
            jobStartTime = DateTime.Now;

            using (CurrentUnitOfWork.SetTenantId(args.tenantId))
            {
                using (_abpSession.Use(args.tenantId, args.userId))
                {
                    UserIdentifier _userId = new UserIdentifier(args.tenantId, (long)args.userId);
                    string userLanguage = _settingManager.GetSettingValueForUser(LocalizationSettingNames.DefaultLanguage, _userId);
                    CultureInfo culture = CultureHelper.GetCultureInfoByChecking(userLanguage);
                    Thread.CurrentThread.CurrentUICulture = culture;
                    LocalizationSourceName = ngTTMConsts.LocalizationSourceName;

                    Execute_MyCode(args);       // How to "fire and forget" this?  Or an instance of the CreateExamJob class?
                }
            }
        }
    }

==========================================

    [Serializable]
    public class CreatJobArgs
    {
        public string fileToken { get; set; }
        public string folder { get; set; }
        public string jobGuid { get; set; }
        public string operation { get; set; }
        public int? tenantId { get; set; }
        public long? userId { get; set; }
        public string workingDirectory { get; set; }
    }

5 Answer(s)
  • User Avatar
    0
    zony created
    Support Team

    Hi timmackey, The default background job manager of ABP is blocking, it can only execute one job at a time. Currently, there is no implementation of the CancellationToken mechanism, so long-blocking jobs cannot be interrupted. You can set different priorities for different tasks to alleviate this situation. If you want to execute tasks in parallel, or temporarily cancel tasks, you can use Abp.Hangfire. Hangfire reference:

  • User Avatar
    0
    timmackey created

    @zony I'm not seeing a path forward due to the limitations of Hangfire Background Methods, described here: https://docs.hangfire.io/en/latest/background-methods/index.html ...which states

    Local variables, instance and static fields and other information isn’t available in our background jobs.

    My background process requires injected dependencies for ILocalizationManager, IBinaryObjectManager, and others. (Usage is not shown in the code snippets posted) Unless there is something I'm not understanding, Hangfire doesn't appear to be a viable option.

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @timmackey,

    You can use dependency injection in Hagfire, see https://docs.hangfire.io/en/latest/background-methods/using-ioc-containers.html

  • User Avatar
    0
    timmackey created

    Hi @ismcagdas, Thanks for the link.

    In thinking more about my requirements, I'm coming to the conclusion that deferring one job over another depending on demand and job type, may not be the optimal solution. Plus, the solutions recommended require Hangfire.Ace, annual license fee $1500.

    I'm considering Azure Functions, which is an elastic on-demand pay-as-you-go n-instance service; more like what I'm looking for.

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @timmackey

    This is the first time I see Hangfire.Ace. You can also take a look at https://www.quartz-scheduler.net/, this is also a very nice library.