Skip to content

Commit 7aeb304

Browse files
committed
Some redesign
1 parent 20d2bd9 commit 7aeb304

15 files changed

+124
-114
lines changed

README.md

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Written for **ASP.NET Core** (ASP.NET 5, ASP.NET vNext).
1515
## Main features
1616

1717
* Start and Stop your task at any time;
18+
* CancelationToken may be used for Stopping;
1819
* First run (after Start) is delayed at random value (10-30 sec, customizable) to prevent app freeze during statup;
1920
* Run "immediately" (without waiting for next scheduled time);
2021
* Change run interval while running;
@@ -40,7 +41,7 @@ public class MyFirstTask : IRunnable
4041
this.logger = logger;
4142
}
4243

43-
public void Run(ITask currentTask, CancellationToken cancellationToken)
44+
public Task RunAsync(ITask currentTask, IServiceProvider scopeServiceProvider, CancellationToken cancellationToken)
4445
{
4546
// Place your code here
4647
}
@@ -49,6 +50,8 @@ public class MyFirstTask : IRunnable
4950

5051
You can add any parameters to constructor, while they are resolvable from DI container (including scope-lifetime services, because new scope is created for every task run).
5152

53+
By default, new instance of `IRunnable` is created for every task run, but you may change lifetime in `AddTask` (see below). Use `IServiceProvider` passed to `RunAsync` to obtain scope-wide services if you force your task be singleton.
54+
5255
### 2. Register and start your task in `Startup.cs`
5356

5457

@@ -70,11 +73,28 @@ public void Configure(IApplicationBuilder app, ...)
7073

7174
And voila! Your task will run every 5 minutes. Until your application ends, of course.
7275

76+
`AddTask` adds your `MyFirstTask` to DI container with transient lifetime (new instance will be created for every task run). Pass desired lifetime to `AddTask()` to override: `services.AddTask<MyFirstTask>(ServiceLifetime.Singleton)`.
77+
78+
### Run immediately
79+
80+
Anywhere in you app:
81+
82+
```csharp
83+
// obtain reference to your task
84+
var myTask = serviceProvider.GetService<ITask<MyFirstTask>>();
85+
86+
// poke it
87+
if (myTask.IsStarted)
88+
{
89+
myTask.TryRunImmediately();
90+
}
91+
```
92+
7393
## Installation
7494

7595
Use NuGet package [RecurrentTasks](https://www.nuget.org/packages/RecurrentTasks/)
7696

77-
Target [framework/platform moniker](https://github.com/dotnet/corefx/blob/master/Documentation/architecture/net-platform-standard.md): **`netstandard1.3`**
97+
Target [framework/platform moniker](https://github.com/dotnet/corefx/blob/master/Documentation/architecture/net-platform-standard.md): **`net451`**, **`net46`**, **`netstandard1.3`**, **`netstandard2.0`**
7898

7999
### Dependencies
80100

sample/RecurrentTasks.Sample/RecurrentTasks.Sample.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk.Web">
22
<PropertyGroup>
3-
<TargetFramework>netcoreapp1.0</TargetFramework>
3+
<TargetFramework>netcoreapp2.0</TargetFramework>
44
<PreserveCompilationContext>true</PreserveCompilationContext>
55
<AssemblyName>RecurrentTasks.Sample</AssemblyName>
66
<OutputType>Exe</OutputType>

sample/RecurrentTasks.Sample/SampleTask.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
{
33
using System;
44
using System.Threading;
5+
using System.Threading.Tasks;
56
using Microsoft.Extensions.Logging;
67

78
public class SampleTask : IRunnable
@@ -16,14 +17,15 @@ public SampleTask(ILogger<SampleTask> logger, SampleTaskRunHistory runHistory)
1617
this.runHistory = runHistory;
1718
}
1819

19-
public void Run(ITask currentTask, CancellationToken cancellationToken)
20+
public Task RunAsync(ITask currentTask, IServiceProvider scopeServiceProvider, CancellationToken cancellationToken)
2021
{
2122
var msg = string.Format("Run at: {0}", DateTimeOffset.Now);
2223
runHistory.Messages.Add(msg);
2324
logger.LogDebug(msg);
2425

2526
// You can change interval for [all] next runs!
2627
currentTask.Interval = currentTask.Interval.Add(TimeSpan.FromSeconds(1));
28+
return Task.CompletedTask;
2729
}
2830
}
2931
}

src/RecurrentTasks/ExceptionEventArgs.cs

Lines changed: 0 additions & 20 deletions
This file was deleted.

src/RecurrentTasks/IRunnable.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
{
33
using System;
44
using System.Threading;
5+
using System.Threading.Tasks;
56

67
public interface IRunnable
78
{
8-
void Run(ITask currentTask, CancellationToken cancellationToken);
9+
Task RunAsync(ITask currentTask, IServiceProvider scopeServiceProvider, CancellationToken cancellationToken);
910
}
1011
}

src/RecurrentTasks/ITask.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,25 @@
22
{
33
using System;
44
using System.Globalization;
5+
using System.Threading;
6+
using System.Threading.Tasks;
57

68
public interface ITask
79
{
810
/// <summary>
911
/// Called before Run() is called (even before IsRunningRightNow set to true).
1012
/// </summary>
11-
event EventHandler<ServiceProviderEventArgs> BeforeRun;
13+
Func<IServiceProvider, ITask, Task> BeforeRunAsync { get; set; }
1214

1315
/// <summary>
1416
/// Called after Run() sucessfully finished (after IsRunningRightNow set to false)
1517
/// </summary>
16-
event EventHandler<ServiceProviderEventArgs> AfterRunSuccess;
18+
Func<IServiceProvider, ITask, Task> AfterRunSuccessAsync { get; set; }
1719

1820
/// <summary>
1921
/// Called after Run() failed (after IsRunningRightNow set to false)
2022
/// </summary>
21-
event EventHandler<ExceptionEventArgs> AfterRunFail;
23+
Func<IServiceProvider, ITask, Exception, Task> AfterRunFailAsync { get; set; }
2224

2325
/// <summary>
2426
/// <b>true</b> when task is started and will run with specified intervals
@@ -59,6 +61,14 @@ public interface ITask
5961
/// <exception cref="InvalidOperationException">Task is already started</exception>
6062
void Start(TimeSpan firstRunDelay);
6163

64+
/// <summary>
65+
/// Start task (and delay first run for specified interval) with cancellation token (instead of <see cref="Stop"/>)
66+
/// </summary>
67+
/// <param name="firstRunDelay">Delay before first task run (use TimeSpan.Zero for no delay)</param>
68+
/// <param name="cancellationToken">Cancellation Token</param>
69+
/// <exception cref="InvalidOperationException">Task is already started</exception>
70+
void Start(TimeSpan firstRunDelay, CancellationToken cancellationToken);
71+
6272
/// <summary>
6373
/// Stop task (will NOT break if currently running)
6474
/// </summary>

src/RecurrentTasks/RecurrentTasks.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3-
<Copyright>Dmitry Popov, 2016</Copyright>
3+
<Copyright>Dmitry Popov, 2016-2018</Copyright>
44
<AssemblyTitle>RecurrentTasks</AssemblyTitle>
5-
<TargetFrameworks>net451;net46;netstandard1.3</TargetFrameworks>
5+
<TargetFrameworks>net451;net46;netstandard1.3;netstandard2.0</TargetFrameworks>
66
<NetStandardImplicitPackageVersion>1.6.0</NetStandardImplicitPackageVersion>
77
<AssemblyName>RecurrentTasks</AssemblyName>
88
<PackageId>RecurrentTasks</PackageId>
99
<PackageTags>task;job;recurrent;recurring;aspnetcore</PackageTags>
10-
<PackageReleaseNotes>`CancellationToken` added to `IRunnable.Run` (it becomes cancelled when `ITask.Stop()` is called).</PackageReleaseNotes>
10+
<PackageReleaseNotes>https://github.com/justdmitry/RecurrentTasks/releases/tag/v5.0.0</PackageReleaseNotes>
1111
<PackageProjectUrl>https://github.com/justdmitry/RecurrentTasks</PackageProjectUrl>
1212
<PackageLicenseUrl>https://github.com/justdmitry/RecurrentTasks/blob/master/LICENSE</PackageLicenseUrl>
1313
<RepositoryType>git</RepositoryType>
1414
<RepositoryUrl>https://github.com/justdmitry/RecurrentTasks.git</RepositoryUrl>
15-
<Version>4.0.0</Version>
15+
<Version>5.0.0</Version>
1616
<Description>RecurrentTasks for .NET allows you to run simple recurrent background tasks with specific intervals, without complex frameworks, persistance, etc...</Description>
1717
<Authors>just_dmitry</Authors>
1818
<Company />

src/RecurrentTasks/RecurrentTasksServiceCollectionExtensions.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,20 @@
66

77
public static class RecurrentTasksServiceCollectionExtensions
88
{
9-
public static IServiceCollection AddTask<TRunnable>(this IServiceCollection services)
9+
public static IServiceCollection AddTask<TRunnable>(this IServiceCollection services, ServiceLifetime runnableLifetime = ServiceLifetime.Transient)
1010
where TRunnable : IRunnable
1111
{
1212
if (services == null)
1313
{
1414
throw new ArgumentNullException(nameof(services));
1515
}
1616

17+
var runnableType = typeof(TRunnable);
18+
1719
// Register TRunnable in DI container, if not registered already
18-
if (!services.Any(x => x.ServiceType == typeof(TRunnable)))
20+
if (!services.Any(x => x.ServiceType == runnableType))
1921
{
20-
services.AddTransient(typeof(TRunnable));
22+
services.Add(new ServiceDescriptor(runnableType, runnableType, runnableLifetime));
2123
}
2224

2325
services.AddSingleton<ITask<TRunnable>, TaskRunner<TRunnable>>();

src/RecurrentTasks/ServiceProviderEventArgs.cs

Lines changed: 0 additions & 19 deletions
This file was deleted.

src/RecurrentTasks/TaskRunner.cs

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@ public TaskRunner(ILoggerFactory loggerFactory, IServiceScopeFactory serviceScop
3131
}
3232

3333
/// <inheritdoc />
34-
public event EventHandler<ServiceProviderEventArgs> BeforeRun;
34+
public Func<IServiceProvider, ITask, Task> BeforeRunAsync { get; set; }
3535

3636
/// <inheritdoc />
37-
public event EventHandler<ExceptionEventArgs> AfterRunFail;
37+
public Func<IServiceProvider, ITask, Task> AfterRunSuccessAsync { get; set; }
3838

3939
/// <inheritdoc />
40-
public event EventHandler<ServiceProviderEventArgs> AfterRunSuccess;
40+
public Func<IServiceProvider, ITask, Exception, Task> AfterRunFailAsync { get; set; }
4141

4242
/// <inheritdoc />
4343
public TaskRunStatus RunStatus { get; protected set; }
@@ -64,6 +64,12 @@ public bool IsStarted
6464

6565
/// <inheritdoc />
6666
public void Start(TimeSpan firstRunDelay)
67+
{
68+
Start(firstRunDelay, CancellationToken.None);
69+
}
70+
71+
/// <inheritdoc />
72+
public void Start(TimeSpan firstRunDelay, CancellationToken cancellationToken)
6773
{
6874
if (firstRunDelay < TimeSpan.Zero)
6975
{
@@ -81,8 +87,9 @@ public void Start(TimeSpan firstRunDelay)
8187
throw new InvalidOperationException("Already started");
8288
}
8389

84-
cancellationTokenSource = new CancellationTokenSource();
85-
mainTask = Task.Run(() => MainLoop(firstRunDelay));
90+
cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
91+
92+
mainTask = Task.Run(() => MainLoop(firstRunDelay, cancellationTokenSource.Token));
8693
}
8794

8895
/// <inheritdoc />
@@ -108,19 +115,18 @@ public void TryRunImmediately()
108115
runImmediately.Set();
109116
}
110117

111-
protected void MainLoop(TimeSpan firstRunDelay)
118+
protected void MainLoop(TimeSpan firstRunDelay, CancellationToken cancellationToken)
112119
{
113120
logger.LogInformation("MainLoop() started. Running...");
114-
var events = new WaitHandle[] { cancellationTokenSource.Token.WaitHandle, runImmediately };
115121
var sleepInterval = firstRunDelay;
122+
var handles = new[] { cancellationToken.WaitHandle, runImmediately };
116123
while (true)
117124
{
118125
logger.LogDebug("Sleeping for {0}...", sleepInterval);
119126
RunStatus.NextRunTime = DateTimeOffset.Now.Add(sleepInterval);
120-
var signaled = WaitHandle.WaitAny(events, sleepInterval);
127+
WaitHandle.WaitAny(handles, sleepInterval);
121128

122-
// index of signalled handler. zero is for 'cancellationToken'
123-
if (signaled == 0)
129+
if (cancellationToken.IsCancellationRequested)
124130
{
125131
// must stop and quit
126132
logger.LogWarning("CancellationToken signaled, stopping...");
@@ -155,7 +161,7 @@ protected void MainLoop(TimeSpan firstRunDelay)
155161
var runnable = (TRunnable)scope.ServiceProvider.GetRequiredService(typeof(TRunnable));
156162

157163
logger.LogInformation("Calling Run()...");
158-
runnable.Run(this, cancellationTokenSource.Token);
164+
runnable.RunAsync(this, scope.ServiceProvider, cancellationToken).GetAwaiter().GetResult();
159165
logger.LogInformation("Done.");
160166

161167
RunStatus.LastRunTime = startTime;
@@ -204,16 +210,19 @@ protected void MainLoop(TimeSpan firstRunDelay)
204210
}
205211

206212
/// <summary>
207-
/// Invokes <see cref="BeforeRun"/> event (don't forget to call base.OnBeforeRun in override)
213+
/// Invokes <see cref="BeforeRunAsync"/> handler (don't forget to call base.OnBeforeRun in override)
208214
/// </summary>
209215
/// <param name="serviceProvider"><see cref="IServiceProvider"/> to be passed in event args</param>
210216
protected virtual void OnBeforeRun(IServiceProvider serviceProvider)
211217
{
212-
BeforeRun?.Invoke(this, new ServiceProviderEventArgs(serviceProvider));
218+
if (BeforeRunAsync != null)
219+
{
220+
BeforeRunAsync(serviceProvider, this).GetAwaiter().GetResult();
221+
}
213222
}
214223

215224
/// <summary>
216-
/// Invokes <see cref="AfterRunSuccess"/> event (don't forget to call base.OnAfterRunSuccess in override)
225+
/// Invokes <see cref="AfterRunSuccessAsync"/> handler (don't forget to call base.OnAfterRunSuccess in override)
217226
/// </summary>
218227
/// <param name="serviceProvider"><see cref="IServiceProvider"/> to be passed in event args</param>
219228
/// <remarks>
@@ -223,7 +232,10 @@ protected virtual void OnAfterRunSuccess(IServiceProvider serviceProvider)
223232
{
224233
try
225234
{
226-
AfterRunSuccess?.Invoke(this, new ServiceProviderEventArgs(serviceProvider));
235+
if (AfterRunSuccessAsync != null)
236+
{
237+
AfterRunSuccessAsync(serviceProvider, this).GetAwaiter().GetResult();
238+
}
227239
}
228240
catch (Exception ex2)
229241
{
@@ -232,7 +244,7 @@ protected virtual void OnAfterRunSuccess(IServiceProvider serviceProvider)
232244
}
233245

234246
/// <summary>
235-
/// Invokes <see cref="AfterRunFail"/> event - don't forget to call base.OnAfterRunSuccess in override
247+
/// Invokes <see cref="AfterRunFailAsync"/> handler - don't forget to call base.OnAfterRunSuccess in override
236248
/// </summary>
237249
/// <param name="serviceProvider"><see cref="IServiceProvider"/> to be passed in event args</param>
238250
/// <param name="ex"><see cref="Exception"/> to be passes in event args</param>
@@ -243,7 +255,10 @@ protected virtual void OnAfterRunFail(IServiceProvider serviceProvider, Exceptio
243255
{
244256
try
245257
{
246-
AfterRunFail?.Invoke(this, new ExceptionEventArgs(serviceProvider, ex));
258+
if (AfterRunFailAsync != null)
259+
{
260+
AfterRunFailAsync(serviceProvider, this, ex).GetAwaiter().GetResult();
261+
}
247262
}
248263
catch (Exception ex2)
249264
{

0 commit comments

Comments
 (0)