/// <inheritdoc /> public async Task DispatchAsync(IBackgroundCommand command, CancellationToken cancellationToken = default) { if (command == null) { throw new ArgumentNullException(nameof(command)); } try { var stopwatch = Stopwatch.StartNew(); await _wrappedDispatcher.DispatchAsync(command, cancellationToken); stopwatch.Stop(); var eventTelemetry = new EventTelemetry(BackgroundCommandDispatchedEventName); eventTelemetry.Properties.Add(nameof(command.Id), command.Id); eventTelemetry.Properties.Add(nameof(command.Timestamp), command.Timestamp.ToString("O", CultureInfo.InvariantCulture)); eventTelemetry.Metrics.Add(BackgroundCommandDispatchTimeMetricName, stopwatch.ElapsedMilliseconds); _options.Value.AdditionalProperties?.Invoke(command, eventTelemetry.Properties); _telemetryClient.TrackEvent(eventTelemetry); } catch (Exception ex) { var exceptionTelemetry = new ExceptionTelemetry(ex); exceptionTelemetry.Properties.Add(nameof(command.Id), command.Id); exceptionTelemetry.Properties.Add(nameof(command.Timestamp), command.Timestamp.ToString("O", CultureInfo.InvariantCulture)); _options.Value.AdditionalProperties?.Invoke(command, exceptionTelemetry.Properties); _telemetryClient.TrackException(exceptionTelemetry); throw; } }
/// <inheritdoc /> public async Task ProcessAsync(IBackgroundCommand command, CancellationToken cancellationToken = default) { if (command == null) { throw new ArgumentNullException(nameof(command)); } var handlerType = typeof(IBackgroundCommandHandler <>).MakeGenericType(command.GetType()); var handler = _services.GetService(handlerType); if (handler == null) { throw new BackgroundProcessingException($"Unable to find a suitable handler for command {command} ({command.GetType()})."); } var handleMethod = handlerType.GetMethod("HandleAsync", new[] { command.GetType(), typeof(CancellationToken) }); if (handleMethod == null) { throw new BackgroundProcessingException($"Unable to find proper handle method in {handlerType}."); } await(handleMethod.Invoke(handler, new object[] { command, cancellationToken }) as Task); }
public static string Schedule(this IBackgroundCommand cmd, TimeSpan delay) { if (cmd == null) { throw new ArgumentNullException(nameof(cmd)); } return(BackgroundJob.Schedule(() => cmd.Invoke(cmd.Args), delay)); }
public static string Once(this IBackgroundCommand cmd) { if (cmd == null) { throw new ArgumentNullException(nameof(cmd)); } return(BackgroundJob.Enqueue(() => cmd.Invoke(cmd.Args))); }
/// <summary> /// Aborts any process. /// </summary> public void AbortProcess() { if (thread != null) { thread.Abort(); } thread = null; process = null; }
/// <inheritdoc /> public async Task DispatchAsync(IBackgroundCommand command, CancellationToken cancellationToken = default) { if (command == null) { throw new ArgumentNullException(nameof(command)); } _queue.Enqueue(command); _semaphore.Release(); }
/// <inheritdoc /> public async Task ProcessAsync(IBackgroundCommand command, CancellationToken cancellationToken = default) { try { await _wrappedProcessor.ProcessAsync(command, cancellationToken); } finally { _awaiter.Signal(); } }
/// <summary> /// Initializes a new instance of the <see cref="BackgroundCommandEvent"/> class. /// </summary> /// <param name="command">The <see cref="IBackgroundCommand"/>.</param> /// <param name="status">The <see cref="BackgroundCommandEventStatus"/>.</param> /// <param name="timestamp">The timestamp of the event.</param> /// <param name="exception">The <see cref="Exception"/>, if any.</param> public BackgroundCommandEvent( IBackgroundCommand command, BackgroundCommandEventStatus status, DateTimeOffset timestamp, Exception exception = null) { Command = command ?? throw new ArgumentNullException(nameof(command)); Status = status; Timestamp = timestamp; Exception = exception; }
//# Example of job definition: //# .----------------- minute (0 - 59) //# | .------------- hour (0 - 23) //# | | .---------- day of month (1 - 31) //# | | | .------- month (1 - 12) OR jan,feb,mar,apr ... //# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) //# | | | | | //# * * * * * command to be executed //m/n**** command1 //0 0/10 * * * ? => every 10 minutes /// <summary> /// Cron.Minutely /// Cron.Daily /// ... /// </summary> /// <param name="cmd"></param> /// <param name="cron"></param> public static void Recurring(this IBackgroundCommand cmd, Func <string> cron) { if (cmd == null) { throw new ArgumentNullException(nameof(cmd)); } if (cron == null) { throw new ArgumentNullException(nameof(cron)); } RecurringJob.AddOrUpdate(() => cmd.Invoke(cmd.Args), cron); }
/// <summary> /// Starts the process. /// </summary> /// <param name="process">The process to to be computed.</param> /// <throws cref="System.Exception">Throws if the instance is already occupied by another process.</throws> public void StartProcess(IBackgroundCommand process) { if (thread == null) { this.process = process; thread = new Thread(process.Execute); thread.Start(); } else { throw new System.Exception("Only one background process can be active. Join or Abort active process before starting another one."); } }
/// <inheritdoc /> public async Task DispatchAsync(IBackgroundCommand command, CancellationToken cancellationToken = default) { try { var dispatchEvent = new BackgroundCommandEvent(command, BackgroundCommandEventStatus.Dispatched, DateTimeOffset.UtcNow); await _wrappedDispatcher.DispatchAsync(command, cancellationToken); await _repository.Add(dispatchEvent); } catch (Exception ex) { await _repository.Add(new BackgroundCommandEvent(command, BackgroundCommandEventStatus.Error, DateTimeOffset.UtcNow, ex)); throw; } }
/// <inheritdoc /> public async Task ProcessAsync(IBackgroundCommand command, CancellationToken cancellationToken = default) { try { await _repository.Add(new BackgroundCommandEvent(command, BackgroundCommandEventStatus.Processing, DateTimeOffset.UtcNow), cancellationToken); await _wrappedProcessor.ProcessAsync(command, cancellationToken); await _repository.Add(new BackgroundCommandEvent(command, BackgroundCommandEventStatus.Processed, DateTimeOffset.UtcNow)); } catch (Exception ex) { await _repository.Add(new BackgroundCommandEvent(command, BackgroundCommandEventStatus.Error, DateTimeOffset.UtcNow, ex)); throw; } }
/// <summary> /// Joins the process. /// </summary> /// <throws cref="System.Exception">Throws if no process is waiting to be joined.</throws> public void JoinProcess() { if (thread != null) { if (GetState() == States.WaitingForJoin) { thread.Join(); thread = null; process.OnJoin(); process = null; } else { throw new System.Exception("Process cannot be joined becouse it is not finished."); } } else { throw new System.Exception("No process to join."); } }
/// <inheritdoc /> public async Task DispatchAsync(IBackgroundCommand command, CancellationToken cancellationToken = default) { if (command == null) { throw new ArgumentNullException(nameof(command)); } try { var options = _options.Value; await _queue.AddMessageAsync( new CloudQueueMessage(await _serializer.SerializeAsync(command)), timeToLive : options.TimeToLive, initialVisibilityDelay : options.InitialVisibilityDelay, options : options.QueueRequestOptions, operationContext : options.OperationContextBuilder != null?options.OperationContextBuilder(command) : null, cancellationToken); } catch (Exception ex) { throw new BackgroundProcessingException($"Error while enqueueing command {command}: {ex.Message}", ex); } }
/// <inheritdoc /> public async Task <string> SerializeAsync(IBackgroundCommand command, CancellationToken cancellationToken = default) => JsonConvert.SerializeObject(command, _jsonSerializerSettings);
protected override async Task ExecuteAsync(CancellationToken stoppingToken) { var options = _options.Value; var processMessagesActionBlock = new ActionBlock <CloudQueueMessage>( async message => { IBackgroundCommand command = null; try { command = await _serializer.DeserializeAsync(message.AsString); using (var handlerRuntimeCancellationTokenSource = new CancellationTokenSource(options.MaxHandlerRuntime)) using (var combinedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken, handlerRuntimeCancellationTokenSource.Token)) using (var scope = _services.CreateScope()) { var processor = scope.ServiceProvider.GetRequiredService <IBackgroundProcessor>(); await processor.ProcessAsync(command, combinedCancellationTokenSource.Token); await _queue.DeleteMessageAsync(message); } } catch (Exception ex) { _logger.LogError($"An error occured while processing {command}: {ex.Message}", ex); } }, new ExecutionDataflowBlockOptions { CancellationToken = stoppingToken, BoundedCapacity = options.DegreeOfParallelism, MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded, }); var currentPollingFrequency = options.PollingFrequency; while (!stoppingToken.IsCancellationRequested) { var messages = await _queue.GetMessagesAsync( messageCount : options.MessagesBatchSize, visibilityTimeout : options.MaxHandlerRuntime.Add(options.HandlerCancellationGraceDelay), options : options.QueueRequestOptions, operationContext : options.OperationContextBuilder != null?options.OperationContextBuilder() : null); if (!messages.Any()) { await Task.Delay(currentPollingFrequency); currentPollingFrequency = options.NextPollingFrequency(currentPollingFrequency); continue; } currentPollingFrequency = options.PollingFrequency; foreach (var message in messages) { await processMessagesActionBlock.SendAsync(message, stoppingToken); } } processMessagesActionBlock.Complete(); await processMessagesActionBlock.Completion; }