-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
periodic task implementation seems to work
- Loading branch information
Showing
7 changed files
with
231 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
namespace AspNetCoreTestProject | ||
{ | ||
using System; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using BetterHostedServices; | ||
|
||
public class PrintingPeriodicTask : IPeriodicTask | ||
{ | ||
public Task ExecuteAsync(CancellationToken stoppingToken) | ||
{ | ||
Console.WriteLine("Hello world"); | ||
return Task.CompletedTask; | ||
} | ||
} | ||
|
||
public class CountingPeriodicTask : IPeriodicTask | ||
{ | ||
private SingletonStateHolder singletonStateHolder; | ||
private TransientStateHolder transientStateHolder; | ||
private ScopeStateHolder scopeStateHolder; | ||
|
||
public CountingPeriodicTask(TransientStateHolder transientStateHolder, SingletonStateHolder singletonStateHolder, ScopeStateHolder scopeStateHolder) | ||
{ | ||
this.transientStateHolder = transientStateHolder; | ||
this.singletonStateHolder = singletonStateHolder; | ||
this.scopeStateHolder = scopeStateHolder; | ||
} | ||
|
||
public Task ExecuteAsync(CancellationToken stoppingToken) | ||
{ | ||
this.transientStateHolder.Count += 1; | ||
this.singletonStateHolder.Count += 1; | ||
this.scopeStateHolder.Count += 1; | ||
|
||
var random = new Random(); | ||
if (random.Next(0, 10) < 2) | ||
{ | ||
throw new Exception("Oh no something went horribly wrong."); | ||
} | ||
|
||
Console.WriteLine($"Transient state: {this.transientStateHolder.Count}. Scope state: {this.scopeStateHolder.Count}. Singleton state: {this.singletonStateHolder.Count}"); | ||
return Task.CompletedTask; | ||
} | ||
} | ||
|
||
public class SingletonStateHolder | ||
{ | ||
public int Count { get; set; } = 0; | ||
} | ||
|
||
public class ScopeStateHolder | ||
{ | ||
public int Count { get; set; } = 0; | ||
} | ||
|
||
public class TransientStateHolder | ||
{ | ||
public int Count { get; set; } = 0; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
namespace BetterHostedServices | ||
{ | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
/// <summary> | ||
/// A periodic tasks to be executed by a PeriodicTaskRunnerBackgroundService. | ||
/// This task is re-created with a new scope for each invocation to ExecuteAsync. | ||
/// </summary> | ||
public interface IPeriodicTask | ||
{ | ||
/// <summary> | ||
/// This ExecuteAsync is called by the PeriodicTaskRunnerBackgroundService at a given interval. | ||
/// Note that you will get a newly created IPeriodicTask for each invocation. | ||
/// </summary> | ||
/// <param name="stoppingToken">CancellationToken to listen to if you should abandon your work.</param> | ||
/// <returns>Should return a task that will be awaited by the PeriodicTaskRunnerBackgroundService</returns> | ||
public Task ExecuteAsync(CancellationToken stoppingToken); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
namespace BetterHostedServices | ||
{ | ||
/// <summary> | ||
/// | ||
/// </summary> | ||
public enum PeriodicTaskFailureMode | ||
{ | ||
/// <summary> | ||
/// If this failure mode is set and a task throws an uncaught application | ||
/// the task will crash the application. | ||
/// It has the same behaviour as letting the task bubble up from a CriticalBackgroundService. | ||
/// </summary> | ||
CrashApplication = 1, | ||
|
||
/// <summary> | ||
/// If this failure mode is set and a task throws an uncaught application, the error will be logged via | ||
/// the standard ILogger implementation, and nothing further will be done. | ||
/// The next periodic task will continue as planned. | ||
/// </summary> | ||
RetryLater = 5 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
namespace BetterHostedServices | ||
{ | ||
using System; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Logging; | ||
|
||
/// <summary> | ||
/// | ||
/// </summary> | ||
/// <typeparam name="TPeriodicTask"></typeparam> | ||
public class PeriodicTaskRunnerBackgroundService<TPeriodicTask> : CriticalBackgroundService | ||
where TPeriodicTask : IPeriodicTask | ||
{ | ||
private readonly ILogger<PeriodicTaskRunnerBackgroundService<TPeriodicTask>> logger; | ||
private readonly IServiceProvider serviceProvider; | ||
|
||
private readonly PeriodicTaskFailureMode periodicTaskFailureMode; | ||
private readonly TimeSpan timeBetweenTasks; | ||
|
||
/// <summary> | ||
/// | ||
/// </summary> | ||
/// <param name="applicationEnder"></param> | ||
/// <param name="logger"></param> | ||
/// <param name="serviceProvider"></param> | ||
/// <param name="periodicTaskFailureMode"></param> | ||
/// <param name="timeBetweenTasks"></param> | ||
public PeriodicTaskRunnerBackgroundService( | ||
IApplicationEnder applicationEnder, | ||
ILogger<PeriodicTaskRunnerBackgroundService<TPeriodicTask>> logger, | ||
IServiceProvider serviceProvider, | ||
PeriodicTaskFailureMode periodicTaskFailureMode, | ||
TimeSpan timeBetweenTasks) : base(applicationEnder) | ||
{ | ||
this.logger = logger; | ||
this.serviceProvider = serviceProvider; | ||
this.periodicTaskFailureMode = periodicTaskFailureMode; | ||
this.timeBetweenTasks = timeBetweenTasks; | ||
} | ||
|
||
/// <summary> | ||
/// Executes the given TPeriodicTask in a new scope each time. | ||
/// </summary> | ||
/// <param name="stoppingToken"></param> | ||
protected override async Task ExecuteAsync(CancellationToken stoppingToken) | ||
{ | ||
// Ensure that we can crate the service. Do this synchronously so that we'll fail-fast no matter the failure mode | ||
// if the task can't run at all. | ||
using (var scope = this.serviceProvider.CreateScope()) | ||
{ | ||
_ = scope.ServiceProvider.GetRequiredService<TPeriodicTask>(); | ||
} | ||
|
||
while (!stoppingToken.IsCancellationRequested) | ||
{ | ||
try | ||
{ | ||
using var scope = this.serviceProvider.CreateScope(); | ||
var periodicTask = scope.ServiceProvider.GetRequiredService<TPeriodicTask>(); | ||
await periodicTask.ExecuteAsync(stoppingToken); | ||
} | ||
catch (Exception e) | ||
{ | ||
this.logger.LogError(e, $"Exception while processing message in {typeof(TPeriodicTask)}"); | ||
// If failure mode is set to end application, go through the normal OnError flow that crashes the application. | ||
if (this.periodicTaskFailureMode == PeriodicTaskFailureMode.CrashApplication) | ||
{ | ||
this.OnError(e); | ||
} | ||
|
||
if (this.periodicTaskFailureMode == PeriodicTaskFailureMode.RetryLater) | ||
{ | ||
this.logger.LogWarning(e, $"Exception while processing message in {typeof(TPeriodicTask)}. Retrying in {this.timeBetweenTasks}"); | ||
} | ||
} | ||
|
||
await Task.Delay(this.timeBetweenTasks, stoppingToken); | ||
} | ||
} | ||
} | ||
} |