forked from ddrilling/AsbCloudServer
BackgroundWorkerService cleanup and improve tests
This commit is contained in:
parent
f61db91dd2
commit
06fe0e09ff
@ -1,15 +1,16 @@
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace AsbCloudInfrastructure.Services.Background
|
namespace AsbCloudInfrastructure.Services.Background
|
||||||
{
|
{
|
||||||
# nullable enable
|
# nullable enable
|
||||||
|
/// <summary>
|
||||||
|
/// Сервис для фонового выполнения работы
|
||||||
|
/// </summary>
|
||||||
public class BackgroundWorkerService : BackgroundService
|
public class BackgroundWorkerService : BackgroundService
|
||||||
{
|
{
|
||||||
private static readonly TimeSpan executePeriod = TimeSpan.FromSeconds(10);
|
private static readonly TimeSpan executePeriod = TimeSpan.FromSeconds(10);
|
||||||
@ -34,6 +35,16 @@ namespace AsbCloudInfrastructure.Services.Background
|
|||||||
workQueue.Push(work);
|
workQueue.Push(work);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Удаление работы по ID
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool Delete(string id)
|
||||||
|
{
|
||||||
|
return workQueue.Delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
protected override async Task ExecuteAsync(CancellationToken token)
|
protected override async Task ExecuteAsync(CancellationToken token)
|
||||||
{
|
{
|
||||||
while (!token.IsCancellationRequested)
|
while (!token.IsCancellationRequested)
|
||||||
@ -73,175 +84,5 @@ namespace AsbCloudInfrastructure.Services.Background
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// <para>
|
|
||||||
/// Очередь работ
|
|
||||||
/// </para>
|
|
||||||
/// Не периодические задачи будут возвращаться первыми, как самые приоритетные.
|
|
||||||
/// </summary>
|
|
||||||
public class WorkQueue
|
|
||||||
{
|
|
||||||
private Queue<WorkBase> Primary = new (8);
|
|
||||||
private readonly List<WorkPeriodic> Periodic = new (8);
|
|
||||||
internal TimeSpan MaxTimeToNextWork { get; set; } = TimeSpan.FromSeconds(20);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Добавление работы.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="work"></param>
|
|
||||||
/// <exception cref="ArgumentException">Id mast be unique</exception>
|
|
||||||
public void Push(WorkBase work)
|
|
||||||
{
|
|
||||||
if (Periodic.Any(w => w.Id == work.Id))
|
|
||||||
throw new ArgumentException("work.Id is not unique", nameof(work));
|
|
||||||
|
|
||||||
if (Primary.Any(w => w.Id == work.Id))
|
|
||||||
throw new ArgumentException("work.Id is not unique", nameof(work));
|
|
||||||
|
|
||||||
if (work is WorkPeriodic workPeriodic)
|
|
||||||
{
|
|
||||||
Periodic.Add(workPeriodic);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Primary.Enqueue(work);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Удаление работы по ID
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="id"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public bool Delete(string id)
|
|
||||||
{
|
|
||||||
var workPeriodic = Periodic.FirstOrDefault(w => w.Id == id);
|
|
||||||
if(workPeriodic is not null)
|
|
||||||
{
|
|
||||||
Periodic.Remove(workPeriodic);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var work = Primary.FirstOrDefault(w => w.Id == id);
|
|
||||||
if (work is not null)
|
|
||||||
{
|
|
||||||
Primary = new Queue<WorkBase>(Primary.Where(w => w.Id != id));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// <para>
|
|
||||||
/// Возвращает приоритетную задачу.
|
|
||||||
/// </para>
|
|
||||||
/// <para>
|
|
||||||
/// Если приоритетные закончились, то ищет ближайшую периодическую.
|
|
||||||
/// Если до старта ближайшей периодической работы меньше 20 сек,
|
|
||||||
/// то этой задаче устанавливается время последнего запуска в now и она возвращается.
|
|
||||||
/// Если больше 20 сек, то возвращается null.
|
|
||||||
/// </para>
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="maxTimeToNextWork"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public WorkBase? Pop(TimeSpan? maxTimeToNextWork = null)
|
|
||||||
{
|
|
||||||
if (Primary.Any())
|
|
||||||
return Primary.Dequeue();
|
|
||||||
|
|
||||||
var maxTimeToNextWorkLocal = maxTimeToNextWork ?? MaxTimeToNextWork;
|
|
||||||
var work = GetNextPeriodic();
|
|
||||||
if (work is null || work.NextStart - DateTime.Now > maxTimeToNextWorkLocal)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
work.LastStart = DateTime.Now;
|
|
||||||
return work;
|
|
||||||
}
|
|
||||||
|
|
||||||
private WorkPeriodic? GetNextPeriodic()
|
|
||||||
{
|
|
||||||
var work = Periodic
|
|
||||||
.OrderBy(w => w.NextStart)
|
|
||||||
.FirstOrDefault();
|
|
||||||
return work;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class WorkBase
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Идентификатор работы. Должен быть уникальным. Используется в логах и передается в колбэки.
|
|
||||||
/// </summary>
|
|
||||||
public string Id { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Делегат работы.
|
|
||||||
/// <para>
|
|
||||||
/// Параметры:
|
|
||||||
/// <list type="number">
|
|
||||||
/// <item>
|
|
||||||
/// <term>string</term>
|
|
||||||
/// <description>Id Идентификатор работы</description>
|
|
||||||
/// </item>
|
|
||||||
/// <item>
|
|
||||||
/// <term>IServiceProvider</term>
|
|
||||||
/// <description>Поставщик сервисов</description>
|
|
||||||
/// </item>
|
|
||||||
/// <item>
|
|
||||||
/// <term>CancellationToken</term>
|
|
||||||
/// <description>Токен отмены задачи</description>
|
|
||||||
/// </item>
|
|
||||||
/// </list>
|
|
||||||
/// </para>
|
|
||||||
/// </summary>
|
|
||||||
public Func<string, IServiceProvider, CancellationToken, Task> ActionAsync { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Делегат обработки ошибки.
|
|
||||||
/// Не должен выполняться долго.
|
|
||||||
/// </summary>
|
|
||||||
public Func<string, Exception, CancellationToken, Task>? OnErrorAsync { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// максимально допустимое время выполнения работы
|
|
||||||
/// </summary>
|
|
||||||
public TimeSpan Timeout { get; set; } = TimeSpan.FromMinutes(1);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Фактическое время успешного выполнения работы
|
|
||||||
/// </summary>
|
|
||||||
public TimeSpan? ExecutionTime { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Время последнего запуска
|
|
||||||
/// </summary>
|
|
||||||
public DateTime LastStart { get; set; }
|
|
||||||
|
|
||||||
public WorkBase(string id, Func<string, IServiceProvider, CancellationToken, Task> actionAsync)
|
|
||||||
{
|
|
||||||
Id = id;
|
|
||||||
ActionAsync = actionAsync;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class WorkPeriodic : WorkBase
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Период выполнения задачи
|
|
||||||
/// </summary>
|
|
||||||
public TimeSpan Period { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Время следующего запуска
|
|
||||||
/// </summary>
|
|
||||||
public DateTime NextStart => LastStart + Period;
|
|
||||||
|
|
||||||
public WorkPeriodic(string id, Func<string, IServiceProvider, CancellationToken, Task> actionAsync, TimeSpan period)
|
|
||||||
:base(id, actionAsync)
|
|
||||||
{
|
|
||||||
Period = period;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
}
|
}
|
||||||
|
69
AsbCloudInfrastructure/Services/Background/WorkBase.cs
Normal file
69
AsbCloudInfrastructure/Services/Background/WorkBase.cs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace AsbCloudInfrastructure.Services.Background
|
||||||
|
{
|
||||||
|
#nullable enable
|
||||||
|
/// <summary>
|
||||||
|
/// Класс разовой работы.
|
||||||
|
/// Разовая работа приоритетнее периодической.
|
||||||
|
/// </summary>
|
||||||
|
public class WorkBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Идентификатор работы. Должен быть уникальным. Используется в логах и передается в колбэки.
|
||||||
|
/// </summary>
|
||||||
|
public string Id { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Делегат работы.
|
||||||
|
/// <para>
|
||||||
|
/// Параметры:
|
||||||
|
/// <list type="number">
|
||||||
|
/// <item>
|
||||||
|
/// <term>string</term>
|
||||||
|
/// <description>Id Идентификатор работы</description>
|
||||||
|
/// </item>
|
||||||
|
/// <item>
|
||||||
|
/// <term>IServiceProvider</term>
|
||||||
|
/// <description>Поставщик сервисов</description>
|
||||||
|
/// </item>
|
||||||
|
/// <item>
|
||||||
|
/// <term>CancellationToken</term>
|
||||||
|
/// <description>Токен отмены задачи</description>
|
||||||
|
/// </item>
|
||||||
|
/// </list>
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
public Func<string, IServiceProvider, CancellationToken, Task> ActionAsync { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Делегат обработки ошибки.
|
||||||
|
/// Не должен выполняться долго.
|
||||||
|
/// </summary>
|
||||||
|
public Func<string, Exception, CancellationToken, Task>? OnErrorAsync { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// максимально допустимое время выполнения работы
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan Timeout { get; set; } = TimeSpan.FromMinutes(1);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Фактическое время успешного выполнения работы
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan? ExecutionTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Время последнего запуска
|
||||||
|
/// </summary>
|
||||||
|
public DateTime LastStart { get; set; }
|
||||||
|
|
||||||
|
public WorkBase(string id, Func<string, IServiceProvider, CancellationToken, Task> actionAsync)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
ActionAsync = actionAsync;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#nullable disable
|
||||||
|
}
|
36
AsbCloudInfrastructure/Services/Background/WorkPeriodic.cs
Normal file
36
AsbCloudInfrastructure/Services/Background/WorkPeriodic.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace AsbCloudInfrastructure.Services.Background
|
||||||
|
{
|
||||||
|
#nullable enable
|
||||||
|
/// <summary>
|
||||||
|
/// Класс периодической работы.
|
||||||
|
/// </summary>
|
||||||
|
public class WorkPeriodic : WorkBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Период выполнения задачи
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan Period { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Время следующего запуска
|
||||||
|
/// </summary>
|
||||||
|
public DateTime NextStart => LastStart + Period;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Класс периодической работы
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">Идентификатор работы. Должен быть уникальным. Используется в логах и передается в колбэки</param>
|
||||||
|
/// <param name="actionAsync">Делегат работы</param>
|
||||||
|
/// <param name="period">Период выполнения задачи</param>
|
||||||
|
public WorkPeriodic(string id, Func<string, IServiceProvider, CancellationToken, Task> actionAsync, TimeSpan period)
|
||||||
|
:base(id, actionAsync)
|
||||||
|
{
|
||||||
|
Period = period;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#nullable disable
|
||||||
|
}
|
101
AsbCloudInfrastructure/Services/Background/WorkQueue.cs
Normal file
101
AsbCloudInfrastructure/Services/Background/WorkQueue.cs
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace AsbCloudInfrastructure.Services.Background
|
||||||
|
{
|
||||||
|
#nullable enable
|
||||||
|
/// <summary>
|
||||||
|
/// <para>
|
||||||
|
/// Очередь работ
|
||||||
|
/// </para>
|
||||||
|
/// Не периодические задачи будут возвращаться первыми, как самые приоритетные.
|
||||||
|
/// </summary>
|
||||||
|
class WorkQueue
|
||||||
|
{
|
||||||
|
private Queue<WorkBase> Primary = new (8);
|
||||||
|
private readonly List<WorkPeriodic> Periodic = new (8);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Добавление работы.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="work"></param>
|
||||||
|
/// <exception cref="ArgumentException">Id mast be unique</exception>
|
||||||
|
public void Push(WorkBase work)
|
||||||
|
{
|
||||||
|
if (Periodic.Any(w => w.Id == work.Id))
|
||||||
|
throw new ArgumentException("work.Id is not unique", nameof(work));
|
||||||
|
|
||||||
|
if (Primary.Any(w => w.Id == work.Id))
|
||||||
|
throw new ArgumentException("work.Id is not unique", nameof(work));
|
||||||
|
|
||||||
|
if (work is WorkPeriodic workPeriodic)
|
||||||
|
{
|
||||||
|
Periodic.Add(workPeriodic);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Primary.Enqueue(work);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Удаление работы по ID
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool Delete(string id)
|
||||||
|
{
|
||||||
|
var workPeriodic = Periodic.FirstOrDefault(w => w.Id == id);
|
||||||
|
if(workPeriodic is not null)
|
||||||
|
{
|
||||||
|
Periodic.Remove(workPeriodic);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var work = Primary.FirstOrDefault(w => w.Id == id);
|
||||||
|
if (work is not null)
|
||||||
|
{
|
||||||
|
Primary = new Queue<WorkBase>(Primary.Where(w => w.Id != id));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <para>
|
||||||
|
/// Возвращает приоритетную задачу.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Если приоритетные закончились, то ищет ближайшую периодическую.
|
||||||
|
/// Если до старта ближайшей периодической работы меньше 20 сек,
|
||||||
|
/// то этой задаче устанавливается время последнего запуска в now и она возвращается.
|
||||||
|
/// Если больше 20 сек, то возвращается null.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="maxTimeToNextWork"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public WorkBase? Pop()
|
||||||
|
{
|
||||||
|
if (Primary.Any())
|
||||||
|
return Primary.Dequeue();
|
||||||
|
|
||||||
|
var work = GetNextPeriodic();
|
||||||
|
if (work is null || work.NextStart > DateTime.Now)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
work.LastStart = DateTime.Now;
|
||||||
|
return work;
|
||||||
|
}
|
||||||
|
|
||||||
|
private WorkPeriodic? GetNextPeriodic()
|
||||||
|
{
|
||||||
|
var work = Periodic
|
||||||
|
.OrderBy(w => w.NextStart)
|
||||||
|
.ThenByDescending(w => w.Period)
|
||||||
|
.FirstOrDefault();
|
||||||
|
return work;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#nullable disable
|
||||||
|
}
|
@ -5,7 +5,6 @@ using AsbCloudInfrastructure.Services.Background;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Org.BouncyCastle.Asn1.X509.Qualified;
|
|
||||||
|
|
||||||
namespace AsbCloudWebApi.Tests.ServicesTests
|
namespace AsbCloudWebApi.Tests.ServicesTests
|
||||||
{
|
{
|
||||||
@ -13,6 +12,8 @@ namespace AsbCloudWebApi.Tests.ServicesTests
|
|||||||
{
|
{
|
||||||
private readonly Mock<IServiceProvider> mockServiceProvider;
|
private readonly Mock<IServiceProvider> mockServiceProvider;
|
||||||
private readonly Mock<IServiceScopeFactory> mockServiceScopeFactory;
|
private readonly Mock<IServiceScopeFactory> mockServiceScopeFactory;
|
||||||
|
private readonly TimeSpan period = TimeSpan.FromSeconds(10);
|
||||||
|
private readonly Func<string, IServiceProvider, CancellationToken, Task> someAction = (string id, IServiceProvider scope, CancellationToken token) => Task.CompletedTask;
|
||||||
|
|
||||||
public BackgroundWorkerServiceTest()
|
public BackgroundWorkerServiceTest()
|
||||||
{
|
{
|
||||||
@ -42,7 +43,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Push_makes_primary_work_done()
|
public async Task Makes_primary_work_done()
|
||||||
{
|
{
|
||||||
var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object);
|
var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object);
|
||||||
var workDone = false;
|
var workDone = false;
|
||||||
@ -59,7 +60,19 @@ namespace AsbCloudWebApi.Tests.ServicesTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Push_makes_pperiodic_work_done()
|
public async Task Sets_ExecutionTime_after_work_done()
|
||||||
|
{
|
||||||
|
var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object);
|
||||||
|
var work = new WorkBase("", someAction);
|
||||||
|
backgroundService.Push(work);
|
||||||
|
await backgroundService.StartAsync(CancellationToken.None);
|
||||||
|
await Task.Delay(10);
|
||||||
|
|
||||||
|
Assert.True(work.ExecutionTime > TimeSpan.Zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Makes_periodic_work_done()
|
||||||
{
|
{
|
||||||
var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object);
|
var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object);
|
||||||
var workDone = false;
|
var workDone = false;
|
||||||
@ -70,6 +83,80 @@ namespace AsbCloudWebApi.Tests.ServicesTests
|
|||||||
},
|
},
|
||||||
TimeSpan.FromMilliseconds(10));
|
TimeSpan.FromMilliseconds(10));
|
||||||
backgroundService.Push(work);
|
backgroundService.Push(work);
|
||||||
|
await backgroundService.StartAsync(CancellationToken.None);
|
||||||
|
await Task.Delay(20);
|
||||||
|
|
||||||
|
Assert.True(workDone);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Does_not_start_periodic_work()
|
||||||
|
{
|
||||||
|
var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object);
|
||||||
|
var workDone = false;
|
||||||
|
var work = new WorkPeriodic("", (_, _, _) =>
|
||||||
|
{
|
||||||
|
workDone = true;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
},
|
||||||
|
TimeSpan.FromSeconds(30));
|
||||||
|
work.LastStart = DateTime.Now;
|
||||||
|
backgroundService.Push(work);
|
||||||
|
|
||||||
|
await backgroundService.StartAsync(CancellationToken.None);
|
||||||
|
await Task.Delay(20);
|
||||||
|
|
||||||
|
Assert.False(workDone);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Follows_work_priority()
|
||||||
|
{
|
||||||
|
var order = 0;
|
||||||
|
var work1Order = -1;
|
||||||
|
var work2Order = -1;
|
||||||
|
|
||||||
|
var work1 = new WorkPeriodic("1", (_, _, _) =>
|
||||||
|
{
|
||||||
|
work1Order = order++;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
},
|
||||||
|
TimeSpan.FromMilliseconds(1)
|
||||||
|
);
|
||||||
|
|
||||||
|
var work2 = new WorkBase("2", (_, _, _) =>
|
||||||
|
{
|
||||||
|
work2Order = order++;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
|
|
||||||
|
var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object);
|
||||||
|
backgroundService.Push(work2);
|
||||||
|
backgroundService.Push(work1);
|
||||||
|
|
||||||
|
await backgroundService.StartAsync(CancellationToken.None);
|
||||||
|
await Task.Delay(2_100);
|
||||||
|
|
||||||
|
Assert.True(work2Order < work1Order);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Runs_second_after_delete_first()
|
||||||
|
{
|
||||||
|
var workDone = false;
|
||||||
|
|
||||||
|
var work1 = new WorkBase("1", someAction);
|
||||||
|
var work2 = new WorkPeriodic("2", (_, _, _) =>
|
||||||
|
{
|
||||||
|
workDone = true;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}, TimeSpan.FromMilliseconds(1));
|
||||||
|
|
||||||
|
var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object);
|
||||||
|
backgroundService.Push(work1);
|
||||||
|
backgroundService.Push(work2);
|
||||||
|
backgroundService.Delete("1");
|
||||||
|
|
||||||
await backgroundService.StartAsync(CancellationToken.None);
|
await backgroundService.StartAsync(CancellationToken.None);
|
||||||
await Task.Delay(10);
|
await Task.Delay(10);
|
||||||
|
|
||||||
@ -116,5 +203,18 @@ namespace AsbCloudWebApi.Tests.ServicesTests
|
|||||||
|
|
||||||
Assert.True(work2done);
|
Assert.True(work2done);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Push_not_unique_id_should_throw()
|
||||||
|
{
|
||||||
|
var work1 = new WorkPeriodic("1", someAction, TimeSpan.FromSeconds(30));
|
||||||
|
var work2 = new WorkBase("1", someAction);
|
||||||
|
|
||||||
|
var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object);
|
||||||
|
backgroundService.Push(work1);
|
||||||
|
|
||||||
|
Assert.Throws<ArgumentException>(
|
||||||
|
() => backgroundService.Push(work2));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,109 +0,0 @@
|
|||||||
using AsbCloudInfrastructure.Services.Background;
|
|
||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace AsbCloudWebApi.Tests.ServicesTests
|
|
||||||
{
|
|
||||||
public class BackgroundWorkerService_WorkQueue_Test
|
|
||||||
{
|
|
||||||
private readonly TimeSpan period = TimeSpan.FromSeconds(10);
|
|
||||||
private readonly Func<string, IServiceProvider, CancellationToken, Task> somAction = (string id, IServiceProvider scope, CancellationToken token) => Task.CompletedTask;
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Push_not_unique_id_should_throw()
|
|
||||||
{
|
|
||||||
var work1 = new WorkPeriodic("1", somAction, TimeSpan.FromSeconds(30));
|
|
||||||
var work2 = new WorkBase("1", somAction);
|
|
||||||
|
|
||||||
var queue = new WorkQueue();
|
|
||||||
queue.Push(work1);
|
|
||||||
|
|
||||||
Assert.Throws<ArgumentException>(
|
|
||||||
() => queue.Push(work2));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Pop_should_return_null()
|
|
||||||
{
|
|
||||||
var work1 = new WorkPeriodic("1", somAction, TimeSpan.FromSeconds(30))
|
|
||||||
{ LastStart = DateTime.Now };
|
|
||||||
|
|
||||||
var queue = new WorkQueue();
|
|
||||||
queue.Push(work1);
|
|
||||||
var workpoPoped= queue.Pop();
|
|
||||||
|
|
||||||
Assert.Null(workpoPoped);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Pop_primary_first()
|
|
||||||
{
|
|
||||||
var work1 = new WorkBase("1", somAction);
|
|
||||||
var work2 = new WorkPeriodic("1", somAction, period);
|
|
||||||
|
|
||||||
var queue = new WorkQueue();
|
|
||||||
queue.Push(work2);
|
|
||||||
queue.Push(work1);
|
|
||||||
var workpoPoped= queue.Pop();
|
|
||||||
|
|
||||||
Assert.Equal(work1, workpoPoped);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Pop_second_after_delete_first()
|
|
||||||
{
|
|
||||||
var work1 = new WorkPeriodic("1", somAction, period);
|
|
||||||
var work2 = new WorkPeriodic("2", somAction, period);
|
|
||||||
|
|
||||||
var queue = new WorkQueue();
|
|
||||||
queue.Push(work1);
|
|
||||||
queue.Push(work2);
|
|
||||||
queue.Delete("1");
|
|
||||||
|
|
||||||
var workpoPoped= queue.Pop();
|
|
||||||
|
|
||||||
Assert.Equal(work2, workpoPoped);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Pop_closest_to_nextStart()
|
|
||||||
{
|
|
||||||
var work1 = new WorkPeriodic("1", somAction, period) {
|
|
||||||
LastStart = DateTime.Now,
|
|
||||||
};
|
|
||||||
var work2 = new WorkPeriodic("2", somAction, period);
|
|
||||||
|
|
||||||
var queue = new WorkQueue();
|
|
||||||
queue.Push(work1);
|
|
||||||
queue.Push(work2);
|
|
||||||
|
|
||||||
var workpoPoped= queue.Pop();
|
|
||||||
|
|
||||||
Assert.Equal(work2, workpoPoped);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Pop_closest_to_explicit_nextStart()
|
|
||||||
{
|
|
||||||
var baseTime = DateTime.Now - period;
|
|
||||||
var work1 = new WorkPeriodic("1", somAction, period)
|
|
||||||
{
|
|
||||||
LastStart = baseTime - TimeSpan.FromSeconds(-1),
|
|
||||||
};
|
|
||||||
var work2 = new WorkPeriodic("2", somAction, period)
|
|
||||||
{
|
|
||||||
LastStart = baseTime,
|
|
||||||
};
|
|
||||||
|
|
||||||
var queue = new WorkQueue();
|
|
||||||
queue.Push(work1);
|
|
||||||
queue.Push(work2);
|
|
||||||
|
|
||||||
var workpoPoped= queue.Pop();
|
|
||||||
|
|
||||||
Assert.Equal(work2, workpoPoped);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user