Separate quartz (#516)
* Fix sample Elsa.Samples.FileBasedWorkflow * Separate Quartz from Elsa.Activities.Timers * Incremental work on Hangfire integration * Incremental work on Hangfire integration * Incremental work on Hangfire integration * Incremental work on Hangfire integration * Add CancelTimer * ToCronExpression for Duration * Removed SweepInterval * Synchronize access to scheduler * Refactoring RunHangfireWorkflowJob * Optimize timer activities to always execute when target time stamp lies in past * Refactoring TimersOptionsExtensions * Added common.props and configureawait.props * Rename CancelTimer to ClearTimer * Wrong parameter name in comment * Refactoring TimerConsts and RunQuartzWorkflowJob Co-authored-by: Rafael Carnucci <rafael.carnucci@icn.de> Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
This commit is contained in:
parent
3776b651c3
commit
4274996dd9
27
Elsa.sln
27
Elsa.sln
|
|
@ -141,7 +141,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Samples.RebusWorker",
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Samples.RunChildWorkflowWorker", "src\samples\worker\Elsa.Samples.RunChildWorkflowWorker\Elsa.Samples.RunChildWorkflowWorker.csproj", "{A4DF29BD-95CC-4A86-96EC-FB6692270E68}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Samples.Timers", "src\samples\worker\Elsa.Samples.Timers\Elsa.Samples.Timers.csproj", "{FD2D1BD0-1229-4DCF-BE70-6BFD396DD489}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Samples.Timers.Quartz", "src\samples\worker\Elsa.Samples.Timers\Elsa.Samples.Timers.Quartz.csproj", "{FD2D1BD0-1229-4DCF-BE70-6BFD396DD489}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Samples.WhileLoopWorker", "src\samples\worker\Elsa.Samples.WhileLoopWorker\Elsa.Samples.WhileLoopWorker.csproj", "{EA3832C4-8079-4E84-AF8C-12744E4EFD44}"
|
||||
EndProject
|
||||
|
|
@ -167,6 +167,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ElsaDashboard.Backend", "sr
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ElsaDashboard.WebAssembly", "src\dashboards\blazor\ElsaDashboard.WebAssembly\ElsaDashboard.WebAssembly.csproj", "{0AA2A003-C79E-4B43-803E-E1150254D2B0}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "timers", "timers", "{37D2E628-5CC1-4C8E-8C02-3B0A29E2ACAA}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Activities.Timers.Quartz", "src\activities\Elsa.Activities.Timers.Quartz\Elsa.Activities.Timers.Quartz.csproj", "{08D9CB18-2BFB-4CF2-9763-5EE913F56D3E}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Activities.Timers.Hangfire", "src\activities\Elsa.Activities.Timers.Hangfire\Elsa.Activities.Timers.Hangfire.csproj", "{34FB968B-A147-4811-823D-D206FD0027AD}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.Timers.Hangfire", "src\samples\worker\Elsa.Samples.Timers.Hangfire\Elsa.Samples.Timers.Hangfire.csproj", "{7BDF193D-CD69-4D47-B7BF-A8A1D93EBCB9}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Activities.Entity", "src\activities\Elsa.Activities.Entity\Elsa.Activities.Entity.csproj", "{A0A8431F-3A2F-433A-BB0D-EB9627DFA739}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Samples.EntityChanged", "src\samples\console\Elsa.Samples.EntityChanged\Elsa.Samples.EntityChanged.csproj", "{A3149FE4-FD39-4DC2-AA9F-208C7216BC03}"
|
||||
|
|
@ -403,6 +410,18 @@ Global
|
|||
{0AA2A003-C79E-4B43-803E-E1150254D2B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0AA2A003-C79E-4B43-803E-E1150254D2B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0AA2A003-C79E-4B43-803E-E1150254D2B0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{08D9CB18-2BFB-4CF2-9763-5EE913F56D3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{08D9CB18-2BFB-4CF2-9763-5EE913F56D3E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{08D9CB18-2BFB-4CF2-9763-5EE913F56D3E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{08D9CB18-2BFB-4CF2-9763-5EE913F56D3E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{34FB968B-A147-4811-823D-D206FD0027AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{34FB968B-A147-4811-823D-D206FD0027AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{34FB968B-A147-4811-823D-D206FD0027AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{34FB968B-A147-4811-823D-D206FD0027AD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7BDF193D-CD69-4D47-B7BF-A8A1D93EBCB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7BDF193D-CD69-4D47-B7BF-A8A1D93EBCB9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7BDF193D-CD69-4D47-B7BF-A8A1D93EBCB9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7BDF193D-CD69-4D47-B7BF-A8A1D93EBCB9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A0A8431F-3A2F-433A-BB0D-EB9627DFA739}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A0A8431F-3A2F-433A-BB0D-EB9627DFA739}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A0A8431F-3A2F-433A-BB0D-EB9627DFA739}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
|
@ -435,7 +454,7 @@ Global
|
|||
{1D63E1B4-2386-4BEC-9090-C3F3CBF25703} = {B43B546E-23F3-46E8-ACB7-D04F05CDA180}
|
||||
{B43B546E-23F3-46E8-ACB7-D04F05CDA180} = {DA71CDAA-8DD3-4D5F-9FBD-8E4B37A2D925}
|
||||
{D20FCB88-9DCA-49EA-9CC2-5B94DD935D30} = {B43B546E-23F3-46E8-ACB7-D04F05CDA180}
|
||||
{E4B71DC4-3E73-49C3-9B4E-CA13909F222D} = {B43B546E-23F3-46E8-ACB7-D04F05CDA180}
|
||||
{E4B71DC4-3E73-49C3-9B4E-CA13909F222D} = {37D2E628-5CC1-4C8E-8C02-3B0A29E2ACAA}
|
||||
{C4939482-9447-47D1-B6AF-E9F8C6323841} = {B43B546E-23F3-46E8-ACB7-D04F05CDA180}
|
||||
{5E5E1E84-DDBC-40D6-B891-0D563A15A44A} = {DA71CDAA-8DD3-4D5F-9FBD-8E4B37A2D925}
|
||||
{823F80B6-E241-43F7-83BD-29FBB58A77F9} = {B43B546E-23F3-46E8-ACB7-D04F05CDA180}
|
||||
|
|
@ -500,6 +519,10 @@ Global
|
|||
{CCF8D59E-3672-4238-A900-CE879ABF02D2} = {E02BD53A-5B08-47A0-920E-DD537A6AFAAA}
|
||||
{B5EAB378-0002-47FD-A0C1-8931DB577006} = {E02BD53A-5B08-47A0-920E-DD537A6AFAAA}
|
||||
{0AA2A003-C79E-4B43-803E-E1150254D2B0} = {E02BD53A-5B08-47A0-920E-DD537A6AFAAA}
|
||||
{37D2E628-5CC1-4C8E-8C02-3B0A29E2ACAA} = {B43B546E-23F3-46E8-ACB7-D04F05CDA180}
|
||||
{08D9CB18-2BFB-4CF2-9763-5EE913F56D3E} = {37D2E628-5CC1-4C8E-8C02-3B0A29E2ACAA}
|
||||
{34FB968B-A147-4811-823D-D206FD0027AD} = {37D2E628-5CC1-4C8E-8C02-3B0A29E2ACAA}
|
||||
{7BDF193D-CD69-4D47-B7BF-A8A1D93EBCB9} = {E42743A0-FBDD-4150-9D53-6000496D9B87}
|
||||
{A0A8431F-3A2F-433A-BB0D-EB9627DFA739} = {B43B546E-23F3-46E8-ACB7-D04F05CDA180}
|
||||
{A3149FE4-FD39-4DC2-AA9F-208C7216BC03} = {FC9F520F-BA51-4AD2-BFEE-EF787798E734}
|
||||
{C3842132-35BC-4F6D-85CC-7098887F4307} = {7CD5C8D5-EC78-4A99-A514-F01CA7197AC8}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\..\..\common.props" />
|
||||
<Import Project="..\..\..\configureawait.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<Description>
|
||||
Elsa is a set of workflow libraries and tools that enable lean and mean workflowing capabilities in any .NET Core application.
|
||||
This package provides Hangfire timer provider.
|
||||
|
||||
</Description>
|
||||
<PackageTags>elsa, workflows, timers, background tasks</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Elsa.Activities.Timers\Elsa.Activities.Timers.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.7.18" />
|
||||
<PackageReference Include="ncrontab" Version="3.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
|
||||
using Elsa.Activities.Timers.Hangfire.Jobs;
|
||||
using Elsa.Activities.Timers.Hangfire.Models;
|
||||
|
||||
using Hangfire;
|
||||
|
||||
namespace Elsa.Activities.Timers
|
||||
{
|
||||
public static class IBackgroundJobClientExtensions
|
||||
{
|
||||
public static void ScheduleWorkflow(this IBackgroundJobClient backgroundJobClient, RunHangfireWorkflowJobModel data, DateTimeOffset dateTimeOffset)
|
||||
{
|
||||
backgroundJobClient.UnscheduleJobWhenAlreadyExists(data);
|
||||
backgroundJobClient.Schedule<RunHangfireWorkflowJob>(job => job.ExecuteAsync(data), dateTimeOffset);
|
||||
}
|
||||
|
||||
public static void UnscheduleJobWhenAlreadyExists(this IBackgroundJobClient backgroundJobClient, RunHangfireWorkflowJobModel data)
|
||||
{
|
||||
var identity = data.GetIdentity();
|
||||
var monitor = JobStorage.Current.GetMonitoringApi();
|
||||
var workflowJobType = typeof(RunHangfireWorkflowJob);
|
||||
|
||||
var jobs = monitor.ScheduledJobs(0, int.MaxValue)
|
||||
.Where(x => x.Value.Job.Type == workflowJobType && ((RunHangfireWorkflowJobModel)x.Value.Job.Args[0]).GetIdentity() == identity);
|
||||
|
||||
foreach (var job in jobs)
|
||||
{
|
||||
BackgroundJob.Delete(job.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
using System;
|
||||
|
||||
using Elsa.Activities.Timers.Hangfire.Services;
|
||||
using Elsa.Activities.Timers.Options;
|
||||
using Elsa.Activities.Timers.Services;
|
||||
|
||||
using Hangfire;
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.Activities.Timers.Hangfire.Extensions
|
||||
{
|
||||
public static class TimersOptionsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Add Elsa Hangfire Services for background processing
|
||||
/// </summary>
|
||||
/// <param name="timersOptions"></param>
|
||||
public static void UseHangfire(this TimersOptions timersOptions)
|
||||
{
|
||||
timersOptions.Services
|
||||
.AddSingleton<IWorkflowScheduler, HangfireWorkflowScheduler>()
|
||||
.AddSingleton<ICrontabParser, HangfireCrontabParser>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add Elsa Hangfire Services for background processing and Hangfire Services
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only if Hangfire is not already registered in DI
|
||||
/// </remarks>
|
||||
/// <param name="timersOptions"></param>
|
||||
/// <param name="configure">Hangfire settings</param>
|
||||
public static void UseHangfire(this TimersOptions timersOptions, Action<IGlobalConfiguration> configure)
|
||||
{
|
||||
timersOptions.UseHangfire();
|
||||
|
||||
// Add Hangfire services.
|
||||
timersOptions.Services.AddHangfire(configure);
|
||||
|
||||
// Add the processing server as IHostedService
|
||||
timersOptions.Services.AddHangfireServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ConfigureAwait ContinueOnCapturedContext="false" />
|
||||
</Weavers>
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Elsa.Activities.Timers.Hangfire.Models;
|
||||
using Elsa.Activities.Timers.Services;
|
||||
using Elsa.Models;
|
||||
using Elsa.Persistence;
|
||||
using Elsa.Services;
|
||||
|
||||
using Hangfire;
|
||||
using Hangfire.States;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Elsa.Activities.Timers.Hangfire.Jobs
|
||||
{
|
||||
public class RunHangfireWorkflowJob
|
||||
{
|
||||
private readonly IWorkflowRunner _workflowRunner;
|
||||
private readonly IWorkflowRegistry _workflowRegistry;
|
||||
private readonly IWorkflowInstanceStore _workflowInstanceManager;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public RunHangfireWorkflowJob(IWorkflowRunner workflowRunner, IWorkflowRegistry workflowRegistry, IWorkflowInstanceStore workflowInstanceStore, IServiceProvider serviceProvider)
|
||||
{
|
||||
_workflowRunner = workflowRunner;
|
||||
_workflowRegistry = workflowRegistry;
|
||||
_workflowInstanceManager = workflowInstanceStore;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync(RunHangfireWorkflowJobModel data)
|
||||
{
|
||||
var workflowBlueprint = (await _workflowRegistry.GetWorkflowAsync(data.WorkflowDefinitionId, data.TenantId, VersionOptions.Published))!;
|
||||
|
||||
if(workflowBlueprint == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WorkflowInstance? workflowInstance = null;
|
||||
|
||||
if (data.WorkflowInstanceId == null)
|
||||
{
|
||||
if (workflowBlueprint.IsSingleton == false || await _workflowInstanceManager.GetWorkflowIsAlreadyExecutingAsync(data.TenantId, data.WorkflowDefinitionId) == false)
|
||||
{
|
||||
await _workflowRunner.RunWorkflowAsync(workflowBlueprint, data.ActivityId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
workflowInstance = await GetWorkflowInstanceAsync(data.WorkflowInstanceId);
|
||||
|
||||
if (workflowInstance == null)
|
||||
{
|
||||
var logger = _serviceProvider.GetRequiredService<ILogger<RunHangfireWorkflowJob>>();
|
||||
logger.LogError("Could not run Workflow instance with ID {WorkflowInstanceId} because it is not in the database", data.WorkflowInstanceId);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await _workflowRunner.RunWorkflowAsync(workflowBlueprint, workflowInstance!, data.ActivityId);
|
||||
}
|
||||
|
||||
// If it is a RecurringJob and the instance is null, the timer activity is a start trigger.
|
||||
if (data.IsRecurringJob && (workflowInstance == null || workflowInstance.Status is not (WorkflowStatus.Finished or WorkflowStatus.Cancelled)))
|
||||
{
|
||||
var backgroundJobClient = _serviceProvider.GetRequiredService<IBackgroundJobClient>();
|
||||
var crontabParer = _serviceProvider.GetRequiredService<ICrontabParser>();
|
||||
|
||||
backgroundJobClient.ScheduleWorkflow(data, crontabParer.GetNextOccurrence(data.CronExpression!).ToDateTimeOffset());
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<WorkflowInstance?> GetWorkflowInstanceAsync(string workflowInstanceId)
|
||||
{
|
||||
WorkflowInstance? workflowInstance = null;
|
||||
|
||||
for (var i = 0; i < TimerConsts.MaxRetrayGetWorkflow && workflowInstance == null; i++)
|
||||
{
|
||||
workflowInstance = await _workflowInstanceManager.GetByIdAsync(workflowInstanceId);
|
||||
|
||||
if (workflowInstance == null)
|
||||
{
|
||||
System.Threading.Thread.Sleep(10000);
|
||||
}
|
||||
}
|
||||
|
||||
return workflowInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
|
||||
using NodaTime;
|
||||
|
||||
namespace Elsa.Activities.Timers.Hangfire.Models
|
||||
{
|
||||
public class RunHangfireWorkflowJobModel
|
||||
{
|
||||
public RunHangfireWorkflowJobModel(string workflowDefinitionId, string activityId, string? workflowInstanceId, string? tenantId, string? cronExpression)
|
||||
{
|
||||
WorkflowDefinitionId = workflowDefinitionId;
|
||||
WorkflowInstanceId = workflowInstanceId;
|
||||
ActivityId = activityId;
|
||||
TenantId = tenantId;
|
||||
CronExpression = cronExpression;
|
||||
}
|
||||
|
||||
public string WorkflowDefinitionId { get; set; }
|
||||
public string? WorkflowInstanceId { get; set; }
|
||||
public string ActivityId { get; set; }
|
||||
public string? TenantId { get; set; }
|
||||
public string? CronExpression { get; set; }
|
||||
public bool IsRecurringJob => string.IsNullOrEmpty(CronExpression) == false;
|
||||
|
||||
public string GetIdentity() => $"Elsa-tenant:{TenantId ?? "default"}-workflow-instance:{WorkflowInstanceId ?? WorkflowDefinitionId}-activity:{ActivityId}";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
|
||||
using Elsa.Activities.Timers.Services;
|
||||
|
||||
using NCrontab;
|
||||
|
||||
using NodaTime;
|
||||
|
||||
namespace Elsa.Activities.Timers.Hangfire.Services
|
||||
{
|
||||
public class HangfireCrontabParser : ICrontabParser
|
||||
{
|
||||
public Instant GetNextOccurrence(string cronExpression)
|
||||
{
|
||||
var schedule = CrontabSchedule.Parse(cronExpression, new CrontabSchedule.ParseOptions { IncludingSeconds = true });
|
||||
|
||||
return Instant.FromDateTimeUtc(schedule.GetNextOccurrence(DateTime.Now).ToUniversalTime());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Elsa.Activities.Timers.Hangfire.Models;
|
||||
using Elsa.Activities.Timers.Services;
|
||||
using Elsa.Services.Models;
|
||||
|
||||
using Hangfire;
|
||||
|
||||
using NodaTime;
|
||||
|
||||
namespace Elsa.Activities.Timers.Hangfire.Services
|
||||
{
|
||||
public class HangfireWorkflowScheduler : IWorkflowScheduler
|
||||
{
|
||||
private readonly IBackgroundJobClient _backgroundJobClient;
|
||||
private readonly ICrontabParser _crontabParser;
|
||||
|
||||
public HangfireWorkflowScheduler(IBackgroundJobClient backgroundJobClient, ICrontabParser crontabParser)
|
||||
{
|
||||
_backgroundJobClient = backgroundJobClient;
|
||||
_crontabParser = crontabParser;
|
||||
}
|
||||
|
||||
public Task ScheduleWorkflowAsync(IWorkflowBlueprint workflowBlueprint, string activityId, Instant startAt, Duration interval, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var data = CreateData(workflowBlueprint, activityId: activityId, cronExpression: interval.ToCronExpression());
|
||||
|
||||
_backgroundJobClient.ScheduleWorkflow(data, startAt.ToDateTimeOffset());
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task ScheduleWorkflowAsync(IWorkflowBlueprint workflowBlueprint, string activityId, Instant startAt, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var data = CreateData(workflowBlueprint, activityId);
|
||||
|
||||
_backgroundJobClient.ScheduleWorkflow(data, startAt.ToDateTimeOffset());
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task ScheduleWorkflowAsync(IWorkflowBlueprint workflowBlueprint, string activityId, string cronExpression, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var data = CreateData(workflowBlueprint, activityId, cronExpression: cronExpression);
|
||||
var instant = _crontabParser.GetNextOccurrence(cronExpression);
|
||||
|
||||
_backgroundJobClient.ScheduleWorkflow(data, instant.ToDateTimeOffset());
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task ScheduleWorkflowAsync(IWorkflowBlueprint workflowBlueprint, string workflowInstanceId, string activityId, Instant startAt, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var data = CreateData(workflowBlueprint, activityId, workflowInstanceId);
|
||||
|
||||
_backgroundJobClient.ScheduleWorkflow(data, startAt.ToDateTimeOffset());
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task UnscheduleWorkflowAsync(WorkflowExecutionContext workflowExecutionContext, string activityId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_backgroundJobClient.UnscheduleJobWhenAlreadyExists(
|
||||
CreateData(workflowExecutionContext.WorkflowBlueprint,
|
||||
activityId: activityId,
|
||||
workflowInstanceId: workflowExecutionContext.WorkflowInstance.WorkflowInstanceId)
|
||||
);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private RunHangfireWorkflowJobModel CreateData(IWorkflowBlueprint workflowBlueprint, string activityId, string? workflowInstanceId = null, string? cronExpression = null) => CreateData(workflowBlueprint.Id,activityId, workflowInstanceId, workflowBlueprint.TenantId, cronExpression);
|
||||
private RunHangfireWorkflowJobModel CreateData(string workflowDefinitionId, string activityId, string? workflowInstanceId = null, string? tenantId = null, string? cronExpression = null)
|
||||
{
|
||||
return new RunHangfireWorkflowJobModel(
|
||||
workflowDefinitionId: workflowDefinitionId,
|
||||
activityId: activityId,
|
||||
workflowInstanceId: workflowInstanceId,
|
||||
tenantId: tenantId,
|
||||
cronExpression: cronExpression);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\..\..\common.props" />
|
||||
<Import Project="..\..\..\configureawait.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<Description>
|
||||
Elsa is a set of workflow libraries and tools that enable lean and mean workflowing capabilities in any .NET Core application.
|
||||
This package provides Quartz timer provider.
|
||||
</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
|
||||
<PackageReference Include="Quartz" Version="3.2.3" />
|
||||
<PackageReference Include="Quartz.Extensions.DependencyInjection" Version="3.2.3" />
|
||||
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.2.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Elsa.Activities.Timers\Elsa.Activities.Timers.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
using System;
|
||||
|
||||
using Elsa.Activities.Timers.Options;
|
||||
using Elsa.Activities.Timers.Quartz.Jobs;
|
||||
using Elsa.Activities.Timers.Quartz.Services;
|
||||
using Elsa.Activities.Timers.Services;
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
using Quartz;
|
||||
|
||||
namespace Elsa.Activities.Timers
|
||||
{
|
||||
public static class TimersOptionsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Add Quartz for background processing
|
||||
/// </summary>
|
||||
/// <param name="timersOptions"></param>
|
||||
public static void UseQuartzProvider(this TimersOptions timersOptions, Action<QuartzOptions>? configureOptions = default, Action<IServiceCollectionQuartzConfigurator>? configureQuartz = default)
|
||||
{
|
||||
if (configureOptions != null)
|
||||
timersOptions.Services.Configure(configureOptions);
|
||||
else
|
||||
timersOptions.Services.AddOptions<QuartzOptions>();
|
||||
|
||||
timersOptions.Services.AddQuartz(configure => ConfigureQuartz(configure, configureQuartz))
|
||||
.AddQuartzHostedService(ConfigureQuartzHostedService)
|
||||
.AddSingleton<IWorkflowScheduler, QuartzWorkflowScheduler>()
|
||||
.AddSingleton<ICrontabParser, QuartzCrontabParser>()
|
||||
.AddTransient<RunQuartzWorkflowJob>();
|
||||
}
|
||||
|
||||
private static void ConfigureQuartzHostedService(QuartzHostedServiceOptions options)
|
||||
{
|
||||
options.WaitForJobsToComplete = true;
|
||||
}
|
||||
|
||||
private static void ConfigureQuartz(IServiceCollectionQuartzConfigurator quartz, Action<IServiceCollectionQuartzConfigurator>? configureQuartz)
|
||||
{
|
||||
quartz.UseMicrosoftDependencyInjectionScopedJobFactory(options => options.AllowDefaultConstructor = true);
|
||||
quartz.AddJob<RunQuartzWorkflowJob>(job => job.StoreDurably().WithIdentity(nameof(RunQuartzWorkflowJob)));
|
||||
|
||||
if (configureQuartz != null)
|
||||
{
|
||||
configureQuartz(quartz);
|
||||
}
|
||||
else
|
||||
{
|
||||
quartz.UseSimpleTypeLoader();
|
||||
quartz.UseInMemoryStore();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ConfigureAwait ContinueOnCapturedContext="false" />
|
||||
</Weavers>
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Elsa.Models;
|
||||
using Elsa.Persistence;
|
||||
using Elsa.Persistence.Specifications;
|
||||
|
|
@ -6,16 +8,16 @@ using Elsa.Services;
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Quartz;
|
||||
|
||||
namespace Elsa.Activities.Timers.Jobs
|
||||
namespace Elsa.Activities.Timers.Quartz.Jobs
|
||||
{
|
||||
public class RunWorkflowJob : IJob
|
||||
public class RunQuartzWorkflowJob : IJob
|
||||
{
|
||||
private readonly IWorkflowRunner _workflowRunner;
|
||||
private readonly IWorkflowRegistry _workflowRegistry;
|
||||
private readonly IWorkflowInstanceStore _workflowInstanceManager;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public RunWorkflowJob(IWorkflowRunner workflowRunner, IWorkflowRegistry workflowRegistry, IWorkflowInstanceStore workflowInstanceStore, ILogger<RunWorkflowJob> logger)
|
||||
public RunQuartzWorkflowJob(IWorkflowRunner workflowRunner, IWorkflowRegistry workflowRegistry, IWorkflowInstanceStore workflowInstanceStore, ILogger<RunQuartzWorkflowJob> logger)
|
||||
{
|
||||
_workflowRunner = workflowRunner;
|
||||
_workflowRegistry = workflowRegistry;
|
||||
|
|
@ -51,16 +53,29 @@ namespace Elsa.Activities.Timers.Jobs
|
|||
|
||||
if (workflowInstance == null)
|
||||
{
|
||||
_logger.LogWarning("Could not run Workflow instance with ID {WorkflowInstanceId} because it appears not yet to be persisted in the database. Rescheduling.", workflowInstanceId);
|
||||
var trigger = context.Trigger;
|
||||
await context.Scheduler.UnscheduleJob(trigger.Key, cancellationToken);
|
||||
var newTrigger = trigger.GetTriggerBuilder().StartAt(trigger.StartTimeUtc.AddSeconds(10)).Build();
|
||||
await context.Scheduler.ScheduleJob(newTrigger, cancellationToken);
|
||||
_logger.LogError("Could not run Workflow instance with ID {WorkflowInstanceId} because it is not in the database", data.WorkflowInstanceId);
|
||||
return;
|
||||
}
|
||||
|
||||
await _workflowRunner.RunWorkflowAsync(workflowBlueprint, workflowInstance!, activityId, cancellationToken: cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<WorkflowInstance?> GetWorkflowInstanceAsync(string workflowInstanceId, CancellationToken cancellationToken)
|
||||
{
|
||||
WorkflowInstance? workflowInstance = null;
|
||||
|
||||
for (var i = 0; i < TimerConsts.MaxRetrayGetWorkflow && workflowInstance == null; i++)
|
||||
{
|
||||
workflowInstance = await _workflowInstanceManager.GetByIdAsync(workflowInstanceId, cancellationToken);
|
||||
|
||||
if (workflowInstance == null)
|
||||
{
|
||||
System.Threading.Thread.Sleep(10000);
|
||||
}
|
||||
}
|
||||
|
||||
return workflowInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
using Elsa.Activities.Timers.Services;
|
||||
|
||||
using NodaTime;
|
||||
|
||||
using Quartz;
|
||||
|
||||
namespace Elsa.Activities.Timers.Quartz.Services
|
||||
{
|
||||
public class QuartzCrontabParser : ICrontabParser
|
||||
{
|
||||
private readonly IClock _clock;
|
||||
|
||||
public QuartzCrontabParser(IClock clock)
|
||||
{
|
||||
_clock = clock;
|
||||
}
|
||||
|
||||
public Instant GetNextOccurrence(string cronExpression)
|
||||
{
|
||||
var schedule = new CronExpression(cronExpression);
|
||||
var now = _clock.GetCurrentInstant();
|
||||
return Instant.FromDateTimeOffset(schedule.GetTimeAfter(now.ToDateTimeOffset())!.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +1,24 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Timers.Jobs;
|
||||
|
||||
using Elsa.Activities.Timers.Quartz.Jobs;
|
||||
using Elsa.Activities.Timers.Services;
|
||||
using Elsa.Services.Models;
|
||||
|
||||
using NodaTime;
|
||||
|
||||
using Quartz;
|
||||
|
||||
namespace Elsa.Activities.Timers.Services
|
||||
namespace Elsa.Activities.Timers.Quartz.Services
|
||||
{
|
||||
public class WorkflowScheduler : IWorkflowScheduler
|
||||
public class QuartzWorkflowScheduler: IWorkflowScheduler
|
||||
{
|
||||
private static readonly string RunWorkflowJobKey = nameof(RunWorkflowJob);
|
||||
private static readonly string RunWorkflowJobKey = nameof(RunQuartzWorkflowJob);
|
||||
private readonly ISchedulerFactory _schedulerFactory;
|
||||
private readonly SemaphoreSlim _semaphore = new(1);
|
||||
|
||||
public WorkflowScheduler(ISchedulerFactory schedulerFactory)
|
||||
public QuartzWorkflowScheduler(ISchedulerFactory schedulerFactory)
|
||||
{
|
||||
_schedulerFactory = schedulerFactory;
|
||||
}
|
||||
|
|
@ -45,10 +49,24 @@ namespace Elsa.Activities.Timers.Services
|
|||
{
|
||||
var trigger = CreateTrigger(workflowBlueprint, activityId, workflowInstanceId)
|
||||
.StartAt(startAt.ToDateTimeOffset()).Build();
|
||||
|
||||
|
||||
await ScheduleJob(trigger, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task UnscheduleWorkflowAsync(WorkflowExecutionContext workflowExecutionContext, string activityId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
|
||||
var trigger = CreateTriggerKey(tenantId: workflowExecutionContext.WorkflowBlueprint.TenantId,
|
||||
workflowDefinitionId: workflowExecutionContext.WorkflowBlueprint.Id,
|
||||
workflowInstanceId: workflowExecutionContext.WorkflowInstance.WorkflowInstanceId,
|
||||
activityId: activityId);
|
||||
|
||||
var existingTrigger = await scheduler.GetTrigger(trigger, cancellationToken);
|
||||
|
||||
if (existingTrigger != null)
|
||||
await scheduler.UnscheduleJob(existingTrigger.Key, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task ScheduleJob(ITrigger trigger, CancellationToken cancellationToken)
|
||||
{
|
||||
await _semaphore.WaitAsync(cancellationToken);
|
||||
|
|
@ -74,15 +92,19 @@ namespace Elsa.Activities.Timers.Services
|
|||
|
||||
private TriggerBuilder CreateTrigger(string? tenantId, string workflowDefinitionId, string? workflowInstanceId, string activityId)
|
||||
{
|
||||
var groupName = $"tenant:{tenantId ?? "default"}-workflow-instance:{workflowInstanceId ?? workflowDefinitionId}";
|
||||
|
||||
return TriggerBuilder.Create()
|
||||
.ForJob(RunWorkflowJobKey)
|
||||
.WithIdentity($"activity:{activityId}", groupName)
|
||||
.WithIdentity(CreateTriggerKey(tenantId, workflowDefinitionId, workflowInstanceId, activityId))
|
||||
.UsingJobData("TenantId", tenantId!)
|
||||
.UsingJobData("WorkflowDefinitionId", workflowDefinitionId)
|
||||
.UsingJobData("WorkflowInstanceId", workflowInstanceId!)
|
||||
.UsingJobData("ActivityId", activityId);
|
||||
}
|
||||
|
||||
private TriggerKey CreateTriggerKey(string? tenantId, string workflowDefinitionId, string? workflowInstanceId, string activityId)
|
||||
{
|
||||
var groupName = $"tenant:{tenantId ?? "default"}-workflow-instance:{workflowInstanceId ?? workflowDefinitionId}";
|
||||
return new TriggerKey($"activity:{activityId}", groupName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
using Elsa.Activities.Timers.Services;
|
||||
using Elsa.ActivityResults;
|
||||
using Elsa.Attributes;
|
||||
using Elsa.Services;
|
||||
using Elsa.Services.Models;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Elsa.Activities.Timers
|
||||
{
|
||||
|
||||
[Trigger(Category = "Timers", Description = "Cancel a timer (Cron, StartAt, Timer) so that it is not executed. ")]
|
||||
public class ClearTimer : Activity
|
||||
{
|
||||
private readonly IWorkflowScheduler _workflowScheduler;
|
||||
|
||||
public ClearTimer(IWorkflowScheduler workflowScheduler)
|
||||
{
|
||||
_workflowScheduler = workflowScheduler;
|
||||
}
|
||||
|
||||
[ActivityProperty(Hint = "The id of the timer (Cron, StartAt, Timer) activity, which is to be cleared")]
|
||||
public string ActivityId { get; set; } = default!;
|
||||
|
||||
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
await _workflowScheduler.UnscheduleWorkflowAsync(context.WorkflowExecutionContext, ActivityId);
|
||||
return Done();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
|
||||
using Elsa.Builders;
|
||||
using Elsa.Services.Models;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Elsa.Activities.Timers
|
||||
{
|
||||
public static class ClearTimerExtensions
|
||||
{
|
||||
public static IActivityBuilder CancelTimer(this IBuilder builder,
|
||||
Action<ISetupActivity<ClearTimer>>? setup = default) => builder.Then(setup);
|
||||
|
||||
public static IActivityBuilder CancelTimer(this IBuilder builder,
|
||||
Func<ActivityExecutionContext, string> activityId) =>
|
||||
builder.CancelTimer(setup => setup.Set(x => x.ActivityId, activityId));
|
||||
|
||||
public static IActivityBuilder CancelTimer(this IBuilder builder, Func<string> activityId) =>
|
||||
builder.CancelTimer(setup => setup.Set(x => x.ActivityId, activityId));
|
||||
|
||||
public static IActivityBuilder CancelTimer(this IBuilder builder, string activityId) =>
|
||||
builder.CancelTimer(setup => setup.Set(x => x.ActivityId, activityId));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Timers.Services;
|
||||
using Elsa.ActivityResults;
|
||||
|
|
@ -5,6 +6,7 @@ using Elsa.Attributes;
|
|||
using Elsa.Persistence;
|
||||
using Elsa.Services;
|
||||
using Elsa.Services.Models;
|
||||
|
||||
using NodaTime;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
|
|
@ -20,12 +22,14 @@ namespace Elsa.Activities.Timers
|
|||
private readonly IClock _clock;
|
||||
private readonly IWorkflowInstanceStore _workflowInstanceStore;
|
||||
private readonly IWorkflowScheduler _workflowScheduler;
|
||||
private readonly ICrontabParser _crontabParser;
|
||||
|
||||
public Cron(IWorkflowInstanceStore workflowInstanceStore, IWorkflowScheduler workflowScheduler, IClock clock)
|
||||
{
|
||||
_clock = clock;
|
||||
_workflowInstanceStore = workflowInstanceStore;
|
||||
_workflowScheduler = workflowScheduler;
|
||||
_crontabParser = crontabParser;
|
||||
}
|
||||
|
||||
[ActivityProperty(Hint = "Specify a CRON expression. See https://crontab.guru/ for help.")]
|
||||
|
|
@ -45,7 +49,7 @@ namespace Elsa.Activities.Timers
|
|||
var cancellationToken = context.CancellationToken;
|
||||
var workflowBlueprint = context.WorkflowExecutionContext.WorkflowBlueprint;
|
||||
var workflowInstance = context.WorkflowExecutionContext.WorkflowInstance;
|
||||
var executeAt = GetNextOccurrence(CronExpression);
|
||||
var executeAt = _crontabParser.GetNextOccurrence(CronExpression);
|
||||
|
||||
ExecuteAt = executeAt;
|
||||
|
||||
|
|
@ -59,12 +63,5 @@ namespace Elsa.Activities.Timers
|
|||
}
|
||||
|
||||
protected override IActivityExecutionResult OnResume() => Done();
|
||||
|
||||
private Instant GetNextOccurrence(string cronExpression)
|
||||
{
|
||||
var schedule = new Quartz.CronExpression(cronExpression);
|
||||
var now = _clock.GetCurrentInstant();
|
||||
return Instant.FromDateTimeOffset(schedule.GetTimeAfter(now.ToDateTimeOffset())!.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,11 @@
|
|||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<Description>
|
||||
Elsa is a set of workflow libraries and tools that enable lean and mean workflowing capabilities in any .NET Core application.
|
||||
</Description>
|
||||
<Copyright>2020</Copyright>
|
||||
<PackageProjectUrl>https://github.com/elsa-workflows/elsa-core</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/elsa-workflows/elsa-core</RepositoryUrl>
|
||||
<RepositoryType>GitHub</RepositoryType>
|
||||
This package provides the following Timer activities:
|
||||
|
||||
* CronEvent
|
||||
|
|
@ -20,11 +25,4 @@
|
|||
<ProjectReference Include="..\..\core\Elsa.Core\Elsa.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
|
||||
<PackageReference Include="Quartz" Version="3.2.3" />
|
||||
<PackageReference Include="Quartz.Extensions.DependencyInjection" Version="3.2.3" />
|
||||
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.2.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
using NodaTime;
|
||||
|
||||
namespace NodaTime
|
||||
{
|
||||
public static class DurationExtensions
|
||||
{
|
||||
public static string ToCronExpression(this Duration duration)
|
||||
{
|
||||
static string CreateCronComponent(int number)
|
||||
{
|
||||
return (number > 0 ? $"*/{number}" : "* ");
|
||||
}
|
||||
|
||||
var cron = CreateCronComponent(duration.Seconds);
|
||||
cron += ' ' + CreateCronComponent(duration.Minutes);
|
||||
cron += ' ' + CreateCronComponent(duration.Hours);
|
||||
cron += ' ' + CreateCronComponent(duration.Days);
|
||||
return cron + " * *";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,56 +1,30 @@
|
|||
using System;
|
||||
|
||||
using Elsa.Activities.Timers;
|
||||
using Elsa.Activities.Timers.HostedServices;
|
||||
using Elsa.Activities.Timers.Jobs;
|
||||
using Elsa.Activities.Timers.Services;
|
||||
using Elsa.Activities.Timers.Options;
|
||||
using Elsa.Activities.Timers.Triggers;
|
||||
using Quartz;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddTimerActivities(this IServiceCollection services, Action<QuartzOptions>? configureOptions = default, Action<IServiceCollectionQuartzConfigurator>? configureQuartz = default)
|
||||
public static IServiceCollection AddTimerActivities(this IServiceCollection services,
|
||||
Action<TimersOptions>? configure = default)
|
||||
{
|
||||
if (configureOptions != null)
|
||||
services.Configure(configureOptions);
|
||||
else
|
||||
services.AddOptions<QuartzOptions>();
|
||||
var options = new TimersOptions(services);
|
||||
configure?.Invoke(options);
|
||||
|
||||
return services
|
||||
.AddQuartz(configure => ConfigureQuartz(configure, configureQuartz))
|
||||
.AddQuartzHostedService(ConfigureQuartzHostedService)
|
||||
.AddSingleton<IWorkflowScheduler, WorkflowScheduler>()
|
||||
.AddTransient<RunWorkflowJob>()
|
||||
return services
|
||||
.AddHostedService<StartJobs>()
|
||||
.AddActivity<Cron>()
|
||||
.AddActivity<Timer>()
|
||||
.AddActivity<StartAt>()
|
||||
.AddActivity<ClearTimer>()
|
||||
.AddTriggerProvider<TimerTriggerProvider>()
|
||||
.AddTriggerProvider<CronTriggerProvider>()
|
||||
.AddTriggerProvider<StartAtTriggerProvider>();
|
||||
}
|
||||
|
||||
private static void ConfigureQuartzHostedService(QuartzHostedServiceOptions options)
|
||||
{
|
||||
options.WaitForJobsToComplete = true;
|
||||
}
|
||||
|
||||
private static void ConfigureQuartz(IServiceCollectionQuartzConfigurator quartz, Action<IServiceCollectionQuartzConfigurator>? configureQuartz)
|
||||
{
|
||||
quartz.UseMicrosoftDependencyInjectionScopedJobFactory(options => options.AllowDefaultConstructor = true);
|
||||
quartz.AddJob<RunWorkflowJob>(job => job.StoreDurably().WithIdentity(nameof(RunWorkflowJob)));
|
||||
|
||||
if (configureQuartz != null)
|
||||
{
|
||||
configureQuartz(quartz);
|
||||
}
|
||||
else
|
||||
{
|
||||
quartz.UseSimpleTypeLoader();
|
||||
quartz.UseInMemoryStore();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,17 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
using NodaTime;
|
||||
|
||||
namespace Elsa.Activities.Timers.Options
|
||||
{
|
||||
public class TimersOptions
|
||||
{
|
||||
public TimersOptions()
|
||||
public TimersOptions(IServiceCollection services)
|
||||
{
|
||||
SweepInterval = Duration.FromMinutes(1);
|
||||
Services = services;
|
||||
}
|
||||
|
||||
public Duration SweepInterval { get; set; }
|
||||
|
||||
public IServiceCollection Services { get; }
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
using NodaTime;
|
||||
|
||||
namespace Elsa.Activities.Timers.Services
|
||||
{
|
||||
/// <summary></summary>
|
||||
/// <remarks>The providers can support different formats. Quartz, for example, supports years.</remarks>
|
||||
public interface ICrontabParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a provider dependent cron string to <see cref="Instant"/>
|
||||
/// </summary>
|
||||
/// <param name="cronExpression"></param>
|
||||
/// <returns></returns>
|
||||
Instant GetNextOccurrence(string cronExpression);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Threading;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Services.Models;
|
||||
using NodaTime;
|
||||
|
|
@ -11,5 +11,6 @@ namespace Elsa.Activities.Timers.Services
|
|||
Task ScheduleWorkflowAsync(IWorkflowBlueprint workflowBlueprint, string activityId, Instant startAt, CancellationToken cancellationToken = default);
|
||||
Task ScheduleWorkflowAsync(IWorkflowBlueprint workflowBlueprint, string activityId, string cronExpression, CancellationToken cancellationToken = default);
|
||||
Task ScheduleWorkflowAsync(IWorkflowBlueprint workflowBlueprint, string workflowInstanceId, string activityId, Instant startAt, CancellationToken cancellationToken = default);
|
||||
Task UnscheduleWorkflowAsync(WorkflowExecutionContext workflowExecutionContext, string activityId, CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
11
src/activities/Elsa.Activities.Timers/TimerConsts.cs
Normal file
11
src/activities/Elsa.Activities.Timers/TimerConsts.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Elsa.Activities.Timers
|
||||
{
|
||||
public static class TimerConsts
|
||||
{
|
||||
public const int MaxRetrayGetWorkflow = 3;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\activities\Elsa.Activities.Timers.Quartz\Elsa.Activities.Timers.Quartz.csproj" />
|
||||
<ProjectReference Include="..\..\..\core\Elsa\Elsa.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using Elsa.Samples.ForkJoinTimerAndSignalHttp.BackgroundTasks;
|
|||
using Elsa.Samples.ForkJoinTimerAndSignalHttp.Workflows;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Elsa.Activities.Timers;
|
||||
|
||||
namespace Elsa.Samples.ForkJoinTimerAndSignalHttp
|
||||
{
|
||||
|
|
@ -15,7 +16,7 @@ namespace Elsa.Samples.ForkJoinTimerAndSignalHttp
|
|||
services
|
||||
.AddElsa()
|
||||
.AddConsoleActivities()
|
||||
.AddTimerActivities()
|
||||
.AddTimerActivities(options => options.UseQuartzProvider())
|
||||
.AddHostedService<WorkflowStarter<DemoWorkflow>>()
|
||||
.AddWorkflow<DemoWorkflow>();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\activities\Elsa.Activities.Timers.Quartz\Elsa.Activities.Timers.Quartz.csproj" />
|
||||
<ProjectReference Include="..\..\..\core\Elsa\Elsa.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using Elsa.Samples.Interrupts.Workflows;
|
|||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Elsa.Activities.Timers;
|
||||
|
||||
namespace Elsa.Samples.Interrupts
|
||||
{
|
||||
|
|
@ -16,7 +17,7 @@ namespace Elsa.Samples.Interrupts
|
|||
.AddElsa()
|
||||
.AddConsoleActivities()
|
||||
.AddHttpActivities()
|
||||
.AddTimerActivities()
|
||||
.AddTimerActivities(options => options.UseQuartzProvider())
|
||||
.AddActivity<Sleep>()
|
||||
.StartWorkflow<InterruptableWorkflow>();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\activities\Elsa.Activities.AzureServiceBus\Elsa.Activities.AzureServiceBus.csproj" />
|
||||
<ProjectReference Include="..\..\..\activities\Elsa.Activities.Timers.Quartz\Elsa.Activities.Timers.Quartz.csproj" />
|
||||
<ProjectReference Include="..\..\..\core\Elsa\Elsa.csproj" />
|
||||
<ProjectReference Include="..\..\..\core\Elsa\Elsa.csproj" />
|
||||
<ProjectReference Include="..\..\..\persistence\Elsa.Persistence.YesSql\Elsa.Persistence.YesSql.csproj" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
using Elsa.Activities.AzureServiceBus.Extensions;
|
||||
using Elsa.Persistence.InMemory;
|
||||
using Elsa.Activities.Timers;
|
||||
using Elsa.Persistence.YesSql.Extensions;
|
||||
using Elsa.Samples.AzureServiceBusWorker.Workflows;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
|
@ -22,7 +22,7 @@ namespace Elsa.Samples.AzureServiceBusWorker
|
|||
services
|
||||
.AddElsa(options => options.UseYesSqlPersistence())
|
||||
.AddConsoleActivities()
|
||||
.AddTimerActivities()
|
||||
.AddTimerActivities(options => options.UseQuartzProvider())
|
||||
.AddAzureServiceBusActivities(options => options.ConnectionString = hostContext.Configuration.GetConnectionString("AzureServiceBus"))
|
||||
.AddWorkflow<ProducerWorkflow>()
|
||||
.AddWorkflow<ConsumerWorkflow>();
|
||||
|
|
|
|||
|
|
@ -7,11 +7,12 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\activities\Elsa.Activities.Rebus\Elsa.Activities.Rebus.csproj"/>
|
||||
<ProjectReference Include="..\..\..\core\Elsa\Elsa.csproj"/>
|
||||
<ProjectReference Include="..\..\..\activities\Elsa.Activities.Rebus\Elsa.Activities.Rebus.csproj" />
|
||||
<ProjectReference Include="..\..\..\activities\Elsa.Activities.Timers.Quartz\Elsa.Activities.Timers.Quartz.csproj" />
|
||||
<ProjectReference Include="..\..\..\core\Elsa\Elsa.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using Elsa.Samples.CustomAttributesChildWorker.Messages;
|
|||
using Elsa.Samples.CustomAttributesChildWorker.Workflows;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Elsa.Activities.Timers;
|
||||
|
||||
namespace Elsa.Samples.CustomAttributesChildWorker
|
||||
{
|
||||
|
|
@ -19,7 +20,7 @@ namespace Elsa.Samples.CustomAttributesChildWorker
|
|||
{
|
||||
services
|
||||
.AddElsa()
|
||||
.AddTimerActivities()
|
||||
.AddTimerActivities(options => options.UseQuartzProvider())
|
||||
.AddConsoleActivities()
|
||||
.AddRebusActivities<OrderReceived>()
|
||||
.AddWorkflow<GenerateOrdersWorkflow>()
|
||||
|
|
|
|||
|
|
@ -7,10 +7,16 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\activities\Elsa.Activities.Timers.Quartz\Elsa.Activities.Timers.Quartz.csproj" />
|
||||
<ProjectReference Include="..\..\..\core\Elsa\Elsa.csproj" />
|
||||
<ProjectReference Include="..\..\..\providers\Elsa.DistributedLocking.AzureBlob\Elsa.DistributedLocking.AzureBlob.csproj" />
|
||||
<ProjectReference Include="..\..\..\activities\Elsa.Activities.Timers\Elsa.Activities.Timers.csproj" />
|
||||
<ProjectReference Include="..\..\..\providers\Elsa.DistributedLocking.Redis\Elsa.DistributedLocking.Redis.csproj" />
|
||||
<ProjectReference Include="..\..\..\providers\Elsa.DistributedLocking.SqlServer\Elsa.DistributedLocking.SqlServer.csproj" />
|
||||
<ProjectReference Include="..\..\..\core\Elsa\Elsa.csproj"/>
|
||||
<ProjectReference Include="..\..\..\activities\Elsa.Activities.Timers\Elsa.Activities.Timers.csproj"/>
|
||||
<ProjectReference Include="..\..\..\locking\Elsa.DistributedLocking.AzureBlob\Elsa.DistributedLocking.AzureBlob.csproj"/>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Timers;
|
||||
|
||||
namespace Elsa.Samples.DistributedLock
|
||||
{
|
||||
|
|
@ -20,7 +21,7 @@ namespace Elsa.Samples.DistributedLock
|
|||
services
|
||||
.AddElsa(options => options.UseRedisLockProvider("localhost:6379,abortConnect=false"))
|
||||
.AddConsoleActivities()
|
||||
.AddTimerActivities()
|
||||
.AddTimerActivities(options => options.UseQuartzProvider())
|
||||
.AddWorkflow<RecurringWorkflow>();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,12 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\activities\Elsa.Activities.Rebus\Elsa.Activities.Rebus.csproj"/>
|
||||
<ProjectReference Include="..\..\..\core\Elsa\Elsa.csproj"/>
|
||||
<ProjectReference Include="..\..\..\activities\Elsa.Activities.Rebus\Elsa.Activities.Rebus.csproj" />
|
||||
<ProjectReference Include="..\..\..\activities\Elsa.Activities.Timers.Quartz\Elsa.Activities.Timers.Quartz.csproj" />
|
||||
<ProjectReference Include="..\..\..\core\Elsa\Elsa.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using Elsa.Samples.MultiTenantChildWorker.Messages;
|
|||
using Elsa.Samples.MultiTenantChildWorker.Workflows;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Elsa.Activities.Timers;
|
||||
|
||||
namespace Elsa.Samples.MultiTenantChildWorker
|
||||
{
|
||||
|
|
@ -19,7 +20,7 @@ namespace Elsa.Samples.MultiTenantChildWorker
|
|||
{
|
||||
services
|
||||
.AddElsa()
|
||||
.AddTimerActivities()
|
||||
.AddTimerActivities(options => options.UseQuartzProvider())
|
||||
.AddConsoleActivities()
|
||||
.AddRebusActivities<OrderReceived>()
|
||||
.AddWorkflow<GenerateOrdersWorkflow>()
|
||||
|
|
|
|||
|
|
@ -7,11 +7,13 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
|
||||
<PackageReference Include="Rebus.AzureServiceBus" Version="8.0.0-a1" />
|
||||
<PackageReference Include="Rebus.AzureServiceBus" Version="8.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\activities\Elsa.Activities.Rebus\Elsa.Activities.Rebus.csproj" />
|
||||
<ProjectReference Include="..\..\..\activities\Elsa.Activities.Timers.Quartz\Elsa.Activities.Timers.Quartz.csproj" />
|
||||
<ProjectReference Include="..\..\..\core\Elsa\Elsa.csproj" />
|
||||
<ProjectReference Include="..\..\..\servicebus\Elsa.Rebus.AzureServiceBus\Elsa.Rebus.AzureServiceBus.csproj" />
|
||||
<ProjectReference Include="..\..\..\servicebus\Elsa.Rebus.RabbitMq\Elsa.Rebus.RabbitMq.csproj" />
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using Elsa.Samples.RebusWorker.Messages;
|
|||
using Elsa.Samples.RebusWorker.Workflows;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Elsa.Activities.Timers;
|
||||
|
||||
namespace Elsa.Samples.RebusWorker
|
||||
{
|
||||
|
|
@ -16,7 +17,7 @@ namespace Elsa.Samples.RebusWorker
|
|||
services
|
||||
.AddElsa()
|
||||
.AddConsoleActivities()
|
||||
.AddTimerActivities()
|
||||
.AddTimerActivities(options => options.UseQuartzProvider())
|
||||
.AddRebusActivities<Greeting>()
|
||||
.AddWorkflow<ProducerWorkflow>()
|
||||
.AddWorkflow<ConsumerWorkflow>();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
using Elsa.Activities.Console;
|
||||
using Elsa.Activities.ControlFlow;
|
||||
using Elsa.Activities.Timers;
|
||||
using Elsa.Builders;
|
||||
using NodaTime;
|
||||
|
||||
namespace Elsa.Samples.Timers
|
||||
{
|
||||
public class CancelTimerWorkflow : IWorkflow
|
||||
{
|
||||
public void Build(IWorkflowBuilder workflow)
|
||||
{
|
||||
workflow
|
||||
.StartAt(SystemClock.Instance.GetCurrentInstant().Plus(Duration.FromSeconds(5)))
|
||||
.WriteLine("CancelTimerWorkflow is executed")
|
||||
.Then<Fork>(
|
||||
activity => activity.Set(x => x.Branches, new HashSet<string>(new[] { "Branch 1", "Branch 2" })),
|
||||
fork =>
|
||||
{
|
||||
fork.When("Branch 1").Timer(Duration.FromSeconds(30)).WithId("timer-1").WriteLine("Should not be executed");
|
||||
fork.When("Branch 2").Timer(Duration.FromSeconds(10))
|
||||
.CancelTimer("timer-1")
|
||||
.WriteLine("Timer-1 was canceled")
|
||||
.Timer(Duration.FromSeconds(40))
|
||||
.WriteLine("CancelTimerWorkflow finished")
|
||||
.Finish();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
using Elsa.Activities.Console;
|
||||
using Elsa.Activities.ControlFlow;
|
||||
using Elsa.Activities.Timers;
|
||||
using Elsa.Builders;
|
||||
|
||||
namespace Elsa.Samples.Timers
|
||||
{
|
||||
public class CronTaskWorkflow : IWorkflow
|
||||
{
|
||||
public void Build(IWorkflowBuilder workflow)
|
||||
{
|
||||
workflow
|
||||
.Cron("0/30 * * * * *")
|
||||
.WriteLine(() => $"CRON event at {DateTime.Now}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<RootNamespace>Elsa.Samples.Timers</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Hangfire.SqlServer" Version="1.7.18" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\activities\Elsa.Activities.Timers.Hangfire\Elsa.Activities.Timers.Hangfire.csproj" />
|
||||
<ProjectReference Include="..\..\..\core\Elsa\Elsa.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
using Elsa.Activities.Console;
|
||||
using Elsa.Activities.Timers;
|
||||
using Elsa.Builders;
|
||||
using NodaTime;
|
||||
|
||||
namespace Elsa.Samples.Timers
|
||||
{
|
||||
/// <summary>
|
||||
/// A workflow that executes only once in the near future.
|
||||
/// </summary>
|
||||
public class OneOffWorkflow : IWorkflow
|
||||
{
|
||||
private readonly Instant _executeAt;
|
||||
|
||||
public OneOffWorkflow(Instant executeAt)
|
||||
{
|
||||
_executeAt = executeAt;
|
||||
}
|
||||
|
||||
public void Build(IWorkflowBuilder workflow)
|
||||
{
|
||||
workflow
|
||||
.StartAt(_executeAt)
|
||||
.WriteLine(context => $"Started at {context.GetService<IClock>().GetCurrentInstant()}. Next event happens 30 seconds from now.")
|
||||
.StartIn(Duration.FromSeconds(30))
|
||||
.WriteLine(context => $"Follow-up occurred at {context.GetService<IClock>().GetCurrentInstant()}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/samples/worker/Elsa.Samples.Timers.Hangfire/Program.cs
Normal file
31
src/samples/worker/Elsa.Samples.Timers.Hangfire/Program.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using NodaTime;
|
||||
using Elsa.Activities.Timers.Hangfire.Extensions;
|
||||
using Hangfire;
|
||||
|
||||
namespace Elsa.Samples.Timers
|
||||
{
|
||||
internal class Program
|
||||
{
|
||||
private static async Task Main() => await CreateHostBuilder().UseConsoleLifetime().Build().RunAsync();
|
||||
|
||||
private static IHostBuilder CreateHostBuilder() =>
|
||||
Host.CreateDefaultBuilder()
|
||||
.ConfigureServices(
|
||||
(context, services) =>
|
||||
{
|
||||
services
|
||||
.AddElsa()
|
||||
.AddConsoleActivities()
|
||||
.AddTimerActivities(options =>
|
||||
options.UseHangfire(configure =>
|
||||
configure.UseSqlServerStorage("Server=(localdb)\\MSSQLLocalDB;Database=ElsaHangfire;Trusted_Connection=True;MultipleActiveResultSets=true")))
|
||||
.AddWorkflow<RecurringTaskWorkflow>()
|
||||
.AddWorkflow<CronTaskWorkflow>()
|
||||
.AddWorkflow<CancelTimerWorkflow>()
|
||||
.AddWorkflow(new OneOffWorkflow(SystemClock.Instance.GetCurrentInstant().Plus(Duration.FromSeconds(5))));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
using Elsa.Activities.Console;
|
||||
using Elsa.Activities.Timers;
|
||||
using Elsa.Builders;
|
||||
using NodaTime;
|
||||
|
||||
namespace Elsa.Samples.Timers
|
||||
{
|
||||
public class RecurringTaskWorkflow : IWorkflow
|
||||
{
|
||||
private readonly IClock _clock;
|
||||
|
||||
public RecurringTaskWorkflow(IClock clock)
|
||||
{
|
||||
_clock = clock;
|
||||
}
|
||||
|
||||
public void Build(IWorkflowBuilder workflow)
|
||||
{
|
||||
workflow
|
||||
.AsSingleton()
|
||||
.Timer(Duration.FromSeconds(30))
|
||||
.WriteLine(context => $"{context.WorkflowExecutionContext.WorkflowInstance.WorkflowInstanceId} triggered by timer at {_clock.GetCurrentInstant()}.")
|
||||
.Timer(Duration.FromSeconds(30))
|
||||
.WriteLine(context => $"{context.WorkflowExecutionContext.WorkflowInstance.WorkflowInstanceId} resumed by timer at {_clock.GetCurrentInstant()}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
using Elsa.Activities.Console;
|
||||
using Elsa.Activities.ControlFlow;
|
||||
using Elsa.Activities.Timers;
|
||||
using Elsa.Builders;
|
||||
using NodaTime;
|
||||
|
||||
namespace Elsa.Samples.Timers
|
||||
{
|
||||
public class CancelTimerWorkflow : IWorkflow
|
||||
{
|
||||
public void Build(IWorkflowBuilder workflow)
|
||||
{
|
||||
workflow
|
||||
.StartAt(SystemClock.Instance.GetCurrentInstant().Plus(Duration.FromSeconds(5)))
|
||||
.WriteLine("CancelTimerWorkflow is executed")
|
||||
.Then<Fork>(
|
||||
activity => activity.Set(x => x.Branches, new HashSet<string>(new[] { "Branch 1", "Branch 2" })),
|
||||
fork =>
|
||||
{
|
||||
fork.When("Branch 1").Timer(Duration.FromSeconds(30)).WithId("timer-1").WriteLine("Should not be executed");
|
||||
fork.When("Branch 2").Timer(Duration.FromSeconds(10))
|
||||
.CancelTimer("timer-1")
|
||||
.WriteLine("Timer-1 was canceled")
|
||||
.Timer(Duration.FromSeconds(40))
|
||||
.WriteLine("CancelTimerWorkflow finished")
|
||||
.Finish();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<RootNamespace>Elsa.Samples.Timers</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -11,6 +12,8 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\activities\Elsa.Activities.Timers.Quartz\Elsa.Activities.Timers.Quartz.csproj" />
|
||||
<ProjectReference Include="..\..\..\core\Elsa\Elsa.csproj" />
|
||||
<ProjectReference Include="..\..\..\core\Elsa\Elsa.csproj" />
|
||||
<ProjectReference Include="..\..\..\persistence\Elsa.Persistence.YesSql\Elsa.Persistence.YesSql.csproj" />
|
||||
</ItemGroup>
|
||||
|
|
@ -3,6 +3,7 @@ using Elsa.Persistence.YesSql.Extensions;
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using NodaTime;
|
||||
using Elsa.Activities.Timers;
|
||||
|
||||
namespace Elsa.Samples.Timers
|
||||
{
|
||||
|
|
@ -18,8 +19,9 @@ namespace Elsa.Samples.Timers
|
|||
services
|
||||
.AddElsa(options => options.UseYesSqlPersistence())
|
||||
.AddConsoleActivities()
|
||||
.AddTimerActivities()
|
||||
.AddTimerActivities(options => options.UseQuartzProvider())
|
||||
.AddWorkflow<RecurringTaskWorkflow>()
|
||||
.AddWorkflow<CancelTimerWorkflow>()
|
||||
//.AddWorkflow<CronTaskWorkflow>()
|
||||
//.AddWorkflow(new OneOffWorkflow(SystemClock.Instance.GetCurrentInstant().Plus(Duration.FromSeconds(5))))
|
||||
;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\activities\Elsa.Activities.Timers.Quartz\Elsa.Activities.Timers.Quartz.csproj" />
|
||||
<ProjectReference Include="..\..\..\core\Elsa\Elsa.csproj" />
|
||||
<ProjectReference Include="..\..\..\core\Elsa\Elsa.csproj" />
|
||||
<ProjectReference Include="..\..\..\persistence\Elsa.Persistence.YesSql\Elsa.Persistence.YesSql.csproj" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ namespace Elsa.Samples.WhileLoopWorker
|
|||
services
|
||||
.AddElsa(options => options.UseYesSqlPersistence())
|
||||
.AddConsoleActivities()
|
||||
.AddTimerActivities()
|
||||
.AddTimerActivities(options => options.UseQuartzProvider())
|
||||
.AddSingleton<PhoneCallService>()
|
||||
.AddHostedService<PhoneCallWorker>()
|
||||
.AddActivity<MakePhoneCall>()
|
||||
|
|
|
|||
Loading…
Reference in a new issue