{"id":21283955,"url":"https://github.com/coronabytes/taskprocessor","last_synced_at":"2025-07-11T11:31:37.052Z","repository":{"id":192166305,"uuid":"543110164","full_name":"coronabytes/taskprocessor","owner":"coronabytes","description":".NET Background Task Processor","archived":false,"fork":false,"pushed_at":"2024-01-05T21:52:35.000Z","size":193,"stargazers_count":2,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-03-26T08:03:48.053Z","etag":null,"topics":["background-jobs","background-tasks","background-thread","cronjob-scheduler","csharp","dotnet","scheduled-jobs","scheduled-tasks"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/coronabytes.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2022-09-29T12:27:19.000Z","updated_at":"2024-04-15T11:35:04.459Z","dependencies_parsed_at":"2023-09-03T03:57:45.586Z","dependency_job_id":"efb61615-219c-4a6a-94e4-5d58518b8e85","html_url":"https://github.com/coronabytes/taskprocessor","commit_stats":{"total_commits":47,"total_committers":2,"mean_commits":23.5,"dds":"0.36170212765957444","last_synced_commit":"6b13e308fa159b37f037329e35067a53fbd7b528"},"previous_names":["coronabytes/taskprocessor"],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coronabytes%2Ftaskprocessor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coronabytes%2Ftaskprocessor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coronabytes%2Ftaskprocessor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coronabytes%2Ftaskprocessor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/coronabytes","download_url":"https://codeload.github.com/coronabytes/taskprocessor/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225715875,"owners_count":17512912,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["background-jobs","background-tasks","background-thread","cronjob-scheduler","csharp","dotnet","scheduled-jobs","scheduled-tasks"],"created_at":"2024-11-21T11:13:17.811Z","updated_at":"2025-07-11T11:31:37.039Z","avatar_url":"https://github.com/coronabytes.png","language":"C#","readme":"[![Nuget](https://img.shields.io/nuget/v/Core.TaskProcessor)](https://www.nuget.org/packages/Core.TaskProcessor)\n[![Nuget](https://img.shields.io/nuget/dt/Core.TaskProcessor)](https://www.nuget.org/packages/Core.TaskProcessor)\n\n```\ndotnet add package Core.TaskProcessor\n```\n\n# .NET Background Task Processing Engine\n*Hangfire Pro Redis* except:\n- open source (Apache 2.0)\n- exclusive redis/valkey/elasticache 6.2+ storage engine (cluster mode supported)\n- multi tenancy\n- global pause and resume of all processing\n- batch cancelation can abort in process tasks \"instantly\"\n- easy to access batch statistics per tenant\n- async extension points with access to batch information\n\n## Initialization in AspNetCore\n\n```csharp\nbuilder.Services.AddTaskProcessor((sp, options) =\u003e\n{\n    options.Redis = \"localhost:6379,abortConnect=false\";\n    options.Prefix = \"{coretask}\"; // redis cluster mode needs single hash slot\n    options.Queues = [\"high\", \"default\", \"low\"]; // pop queues from left to right - first non empty queue wins\n    options.MaxWorkers = 4; // action block concurrency limit\n    options.Retries = 3; // if tasks fails x times its discarded or deadlettered\n    options.Invisibility = TimeSpan.FromMinutes(5); // task will be redelivered when taking longer than this\n    options.BaseFrequency = TimeSpan.FromSeconds(5); // fetches tasks when reactive events failed\n    options.PushbackFrequency = TimeSpan.FromSeconds(10); // how often to run task retry/delay pushbacks\n    options.CleanUpFrequency = TimeSpan.FromMinutes(5); // how often to run batch cleanups\n    options.Retention = TimeSpan.FromDays(7); // batch information will be kept this long\n    options.Deadletter = true; // move failed tasks to deadletter queues\n    options.DeadletterSchedules = false; // ignore deadletter for schedules or unique ones will pause indefinatly\n    options.UseCronSeconds = false; // * * * * *\n    options.OnTaskFailedDelay = (_, retry) =\u003e // delay retry on task failure\n        Task.FromResult(retry switch\n        {\n            2 =\u003e TimeSpan.FromSeconds(5),\n            1 =\u003e TimeSpan.FromSeconds(60),\n            _ =\u003e (TimeSpan?)null\n        });\n    options.OnTaskError = (_, exception) =\u003e\n    {\n        sp.GetRequiredService\u003cILogger\u003cITaskProcessor\u003e\u003e()\n            .LogError(exception, \"Task Error\");\n\n        return Task.CompletedTask;\n    };\n});\nbuilder.Services.AddTaskProcessorExecutor();\n```\n## Enqueue single task\n\n```csharp\nvar taskId = await _processor.EnqueueTaskAsync(\"default\", \"my-tenant\",\n  () =\u003e _someScopedService.DoSomethingAsync(\"hello\", CancellationToken.None),\n  delayUntil: DateTimeOffset.UtcNow.AddSeconds(5));\n```\n\n## Enqueue batch tasks\n\n```csharp\nvar batchId = await _processor.EnqueueBatchAsync(\"default\", \"my-tenant\", batch =\u003e\n{\n    batch.Enqueue(() =\u003e _someScopedService.DoSomethingAsync(\"hello\", CancellationToken.None));\n    \n    batch.Enqueue(() =\u003e _someScopedService.DoSomethingAsync(\"world\", CancellationToken.None), \n      delayUntil: DateTimeOffset.UtcNow.AddSeconds(30));\n\n    batch.ContinueWith(() =\u003e _someScopedService.DoSomething(\"!\"), \"high\");\n})\n```\n\n## What functions can be invoked?\n- static functions\n- functions on types resolveable by the IServiceProvider (scoped interfaces preferred)\n- return type of void or Task\n- all parameters need to be json serializable with the default implementation (custom implementations possible)\n- constant parameters are fastest, yet complex lists and objects are also possible (dynamic invoked)\n- only the first method call will run in background - all parameters will be evaluated at enqueue time\n- beware of functions in strongly named assemblys \n\n## Append tasks to batch (warning: continuations will run only once)\n\n```csharp\nawait _processor.AppendBatchAsync(\"default\", \"my-tenant\", batchId, batch =\u003e\n{\n    batch.Enqueue(() =\u003e _someScopedService.DoSomethingAsync(\"hello 2\", CancellationToken.None));\n    batch.Enqueue(() =\u003e _someScopedService.DoSomethingAsync(\"world 2\", CancellationToken.None));\n})\n```\n\n## Cancel batch\n```csharp\nawait _processor.CancelBatchAsync(batchId);\n```\n\n## Schedule tasks\n\n```csharp\nawait _processor.UpsertScheduleAsync(new ScheduleData\n{\n    Id = \"unique-schedule-id\",\n    Tenant = \"my-tenant\",\n    Queue = \"default\",\n    Scope = \"Send hourly email\",\n    Cron = \"0 */1 * * *\",\n    Timezone = \"Etc/UTC\",\n    Unique = true // if task is enqueued from previous cycle and hasn't completed yet, this cycle will be skipped\n}, () =\u003e _someScopedService.DoSomethingAsync(\"scheduled task\", CancellationToken.None));\n```\n\n## Cancel schedule\n```csharp\nawait proc.CancelScheduleAsync(\"unique-schedule-id\", \"my-tenant\");\n```\n\n## Get runtime infos\n```csharp\nawait proc.GetBatchesAsync(\"my-tenant\", 0, 25);\nawait proc.GetTasksInQueueAsync(\"low\", 0, 25);\n```\n\n## custom background worker\nIf you need to integrate another ioc solution set UseHostedService = false\nand provide a custom one. Executor.InvokeAsync has an Type to instance resolver callback.\n```csharp\nclass CustomExecutorService : BackgroundService\n{\n    private readonly ITaskProcessor _processor;\n    private readonly IServiceProvider _serviceProvider;\n\n    public TaskExecutorService(ITaskProcessor processor, IServiceProvider serviceProvider)\n    {\n        _processor = processor;\n        _serviceProvider = serviceProvider;\n\n        processor.Execute = Execute;\n    }\n\n    private async Task Execute(TaskContext ctx)\n    {\n        await using var scope = _serviceProvider.CreateAsyncScope();\n\n        if (ctx.Topic == \"internal:expression:v1\")\n            await _processor.Executor.InvokeAsync(ctx,\n                    type =\u003e scope.ServiceProvider.GetRequiredService(type))\n                .ConfigureAwait(false);\n    }\n\n    protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n    {\n        await _processor.RunAsync(stoppingToken);\n    }\n}\n```\n\n\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoronabytes%2Ftaskprocessor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcoronabytes%2Ftaskprocessor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoronabytes%2Ftaskprocessor/lists"}