Refactor webStore

This commit is contained in:
Frolov-Nikita 2023-10-08 21:20:28 +05:00
parent 724c7b0cd8
commit 1560c6bf91
No known key found for this signature in database
GPG Key ID: 719E3386D12B0760
16 changed files with 219 additions and 277 deletions

View File

@ -1,8 +1,11 @@
using System;
using System.Diagnostics;
namespace AsbCloudInfrastructure.Background
namespace AsbCloudApp.Data
{
/// <summary>
/// Информация о фоновой работе
/// </summary>
public class BackgroudWorkDto
{
/// <summary>
@ -10,6 +13,9 @@ namespace AsbCloudInfrastructure.Background
/// </summary>
public string Id { get; init; } = null!;
/// <summary>
/// Класс описания состояния
/// </summary>
public class CurrentStateInfo
{
private string state = "start";
@ -37,6 +43,9 @@ namespace AsbCloudInfrastructure.Background
}
}
/// <summary>
/// Прогресс
/// </summary>
public double Progress { get; internal set; } = 0;
/// <summary>
@ -45,7 +54,10 @@ namespace AsbCloudInfrastructure.Background
public DateTime StateUpdate { get; private set; } = DateTime.Now;
}
public class LastErrorInfo: LastCompleteInfo
/// <summary>
/// Инфо о последней ошибке
/// </summary>
public class LastErrorInfo : LastCompleteInfo
{
public LastErrorInfo(CurrentStateInfo state, string errorText)
: base(state)
@ -59,12 +71,15 @@ namespace AsbCloudInfrastructure.Background
public string ErrorText { get; init; } = null!;
}
/// <summary>
/// Инфо о последнем завершении
/// </summary>
public class LastCompleteInfo
{
/// <summary>
/// Дата запуска
/// </summary>
public DateTime Start {get; init;}
public DateTime Start { get; init; }
/// <summary>
/// Дата завершения
@ -126,6 +141,9 @@ namespace AsbCloudInfrastructure.Background
private string WorkNameForTrace => $"Backgroud work:\"{Id}\"";
/// <summary>
/// Обновления состояния при запуске работы
/// </summary>
protected void SetStatusStart()
{
CurrentState = new();
@ -133,6 +151,9 @@ namespace AsbCloudInfrastructure.Background
Trace.TraceInformation($"{WorkNameForTrace} state: starting");
}
/// <summary>
/// Обновления состояния в процессе работы
/// </summary>
protected void UpdateStatus(string newState, double? progress)
{
if (CurrentState is null)
@ -145,17 +166,23 @@ namespace AsbCloudInfrastructure.Background
Trace.TraceInformation($"{WorkNameForTrace} state: {newState}");
}
protected void SetStatusComplete(System.Threading.Tasks.TaskStatus status)
/// <summary>
/// Обновления состояния при успешном завершении работы
/// </summary>
protected void SetStatusComplete()
{
if (CurrentState is null)
return;
LastComplete = new (CurrentState);
LastComplete = new(CurrentState);
CurrentState = null;
CountComplete++;
Trace.TraceInformation($"{WorkNameForTrace} state: completed");
}
/// <summary>
/// Обновления состояния при ошибке в работе
/// </summary>
protected void SetLastError(string errorMessage)
{
if (CurrentState is null)

View File

@ -1,78 +1,49 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Background
namespace AsbCloudInfrastructure.Background;
/// <summary>
/// Сервис для фонового выполнения работы
/// </summary>
public class BackgroundWorker : BackgroundService
{
/// <summary>
/// Сервис для фонового выполнения работы
/// </summary>
public class BackgroundWorker : BackgroundService
private static readonly TimeSpan executePeriod = TimeSpan.FromSeconds(10);
private static readonly TimeSpan minDelay = TimeSpan.FromSeconds(2);
private readonly IServiceProvider serviceProvider;
public WorkStore WorkStore { get; } = new WorkStore();
public Work? CurrentWork;
public BackgroundWorker(IServiceProvider serviceProvider)
{
private static readonly TimeSpan executePeriod = TimeSpan.FromSeconds(10);
private static readonly TimeSpan minDelay = TimeSpan.FromSeconds(2);
private readonly IServiceProvider serviceProvider;
private readonly WorkStore workQueue = new WorkStore();
public string? CurrentWorkId;
this.serviceProvider = serviceProvider;
}
public BackgroundWorker(IServiceProvider serviceProvider)
protected override async Task ExecuteAsync(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
this.serviceProvider = serviceProvider;
}
/// <summary>
/// Добавление задачи в очередь.
/// Не периодические задачи будут выполняться вперед.
/// </summary>
/// <param name="work"></param>
/// <exception cref="ArgumentException">Id mast be unique</exception>
public void Push(WorkBase work)
{
workQueue.Push(work);
}
/// <summary>
/// Проверяет наличие работы с указанным Id
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public bool Contains(string id)
{
return workQueue.Contains(id);
}
/// <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)
{
while (!token.IsCancellationRequested)
var work = WorkStore.GetNext();
if (work is null)
{
var work = workQueue.Pop();
if (work is null)
{
await Task.Delay(executePeriod, token);
continue;
}
CurrentWorkId = work.Id;
using var scope = serviceProvider.CreateScope();
await work.Start(scope.ServiceProvider, token);
CurrentWorkId = null;
await Task.Delay(minDelay, token);
await Task.Delay(executePeriod, token);
continue;
}
CurrentWork = work;
using var scope = serviceProvider.CreateScope();
var result = await work.Start(scope.ServiceProvider, token);
if (!result)
WorkStore.Falled.Add(work);
CurrentWork = null;
await Task.Delay(minDelay, token);
}
}
}

View File

@ -1,4 +1,5 @@
using System;
using AsbCloudApp.Data;
using System;
using System.Threading;
using System.Threading.Tasks;
@ -8,9 +9,9 @@ namespace AsbCloudInfrastructure.Background
/// Класс разовой работы.
/// Разовая работа приоритетнее периодической.
/// </summary>
public class WorkBase : BackgroudWorkDto
public class Work : BackgroudWorkDto
{
internal Func<string, IServiceProvider, Action<string, double?>, CancellationToken, Task> ActionAsync { get; }
private Func<string, IServiceProvider, Action<string, double?>, CancellationToken, Task> ActionAsync { get; }
/// <summary>
/// Делегат обработки ошибки.
@ -48,7 +49,7 @@ namespace AsbCloudInfrastructure.Background
/// </list>
/// </para>
/// </param>
public WorkBase(string id, Func<string, IServiceProvider, Action<string, double?>, CancellationToken, Task> actionAsync)
public Work(string id, Func<string, IServiceProvider, Action<string, double?>, CancellationToken, Task> actionAsync)
{
Id = id;
ActionAsync = actionAsync;
@ -67,7 +68,7 @@ namespace AsbCloudInfrastructure.Background
{
var task = ActionAsync(Id, services, UpdateStatus, token);
await task.WaitAsync(Timeout, token);
SetStatusComplete(task.Status);
SetStatusComplete();
return true;
}
catch (Exception exception)

View File

@ -1,45 +1,42 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Background
namespace AsbCloudInfrastructure.Background;
/// <summary>
/// Класс периодической работы.
/// </summary>
public class WorkPeriodic
{
public Work Work { get; }
/// <summary>
/// Класс периодической работы.
/// Период выполнения задачи
/// </summary>
public class WorkPeriodic : WorkBase
public TimeSpan Period { get; set; }
/// <summary>
/// Время следующего запуска
/// </summary>
public DateTime NextStart
{
/// <summary>
/// Период выполнения задачи
/// </summary>
public TimeSpan Period { get; set; }
/// <summary>
/// Время следующего запуска
/// </summary>
public DateTime NextStart
get
{
get
{
var lastStart = LastComplete?.Start ?? DateTime.MinValue;
if (LastError?.Start > lastStart)
lastStart = LastError.Start;
return lastStart + Period;
}
}
/// <summary>
/// Класс периодической работы
/// </summary>
/// <param name="id">Идентификатор работы. Должен быть уникальным. Используется в логах и передается в колбэки</param>
/// <param name="actionAsync">Делегат работы</param>
/// <param name="period">Период выполнения задачи</param>
public WorkPeriodic(string id, Func<string, IServiceProvider, Action<string, double?>, CancellationToken, Task> actionAsync, TimeSpan period)
: base(id, actionAsync)
{
Period = period;
var lastStart = Work.LastComplete?.Start ?? DateTime.MinValue;
if (Work.LastError?.Start > lastStart)
lastStart = Work.LastError.Start;
return lastStart + Period;
}
}
/// <summary>
/// Класс периодической работы
/// </summary>
/// <param name="id">Идентификатор работы. Должен быть уникальным. Используется в логах и передается в колбэки</param>
/// <param name="actionAsync">Делегат работы</param>
/// <param name="period">Период выполнения задачи</param>
public WorkPeriodic(Work work, TimeSpan period)
{
Work = work;
Period = period;
}
}

View File

@ -2,118 +2,85 @@
using System.Collections.Generic;
using System.Linq;
namespace AsbCloudInfrastructure.Background
namespace AsbCloudInfrastructure.Background;
/// <summary>
/// <para>
/// Очередь работ
/// </para>
/// Не периодические задачи будут возвращаться первыми, как самые приоритетные.
/// </summary>
public class WorkStore
{
private readonly List<WorkPeriodic> periodics = new(8);
/// <summary>
/// <para>
/// Очередь работ
/// </para>
/// Не периодические задачи будут возвращаться первыми, как самые приоритетные.
/// Список периодических задач
/// </summary>
class WorkStore
public IEnumerable<WorkPeriodic> Periodics => periodics;
/// <summary>
/// Работы выполняемые один раз
/// </summary>
public Queue<Work> RunOnceQueue { get; private set; } = new(8);
/// <summary>
/// Завершывшиеся с ошибкой
/// </summary>
public CyclycArray<Work> Falled { get; } = new(16);
public void AddPeriodic(Work work, TimeSpan period)
{
private readonly List<WorkPeriodic> Periodic = new(8);
/// <summary>
/// Работы выполняемые один раз
/// </summary>
public Queue<WorkBase> RunOnceQueue { get; } = new(8);
/// <summary>
/// Работы выполняемые периодически
/// </summary>
public IOrderedEnumerable<WorkPeriodic> Periodics => Periodic.OrderBy(work => work.NextStart);
/// <summary>
/// Завершывшиеся с ошибкой
/// </summary>
public CyclycArray<WorkBase> Falled { get; } = new(16);
/// <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;
}
public bool Contains(string id)
{
var result = false;//Periodic.Any(w => w.Id == id) || Primary.Any(w => w.Id == id);
return result;
}
/// <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;
return work;
}
private WorkPeriodic? GetNextPeriodic()
{
var work = Periodic
.OrderBy(w => w.NextStart)
.ThenByDescending(w => w.Period)
.FirstOrDefault();
return work;
}
var periodic = new WorkPeriodic(work, period);
periodics.Add(periodic);
}
/// <summary>
/// Удаление работы по ID из одноразовой очереди
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public bool TryRemoveFromRunOnceQueue(string id)
{
var work = RunOnceQueue.FirstOrDefault(w => w.Id == id);
if (work is not null)
{
RunOnceQueue = new Queue<Work>(RunOnceQueue.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 Work? GetNext()
{
if (RunOnceQueue.Any())
return RunOnceQueue.Dequeue();
var work = GetNextPeriodic();
if (work is null || work.NextStart > DateTime.Now)
return null;
return work.Work;
}
private WorkPeriodic? GetNextPeriodic()
{
var work = Periodics
.OrderBy(w => w.NextStart)
.FirstOrDefault();
return work;
}
}

View File

@ -16,7 +16,6 @@ namespace AsbCloudInfrastructure.Services.DetectOperations
public static class OperationDetectionWorkFactory
{
private const string workId = "Operation detection";
private static readonly TimeSpan workPeriod = TimeSpan.FromMinutes(30);
private static string progress = "no progress";
private static readonly DetectorAbstract[] detectors = new DetectorAbstract[]
@ -32,18 +31,16 @@ namespace AsbCloudInfrastructure.Services.DetectOperations
//new DetectorTemplatingWhileDrilling(),
};
public static WorkPeriodic MakeWork()
{
var workPeriodic = new WorkPeriodic(workId, WorkAction, workPeriod);
workPeriodic.Timeout = TimeSpan.FromSeconds(15 * 60);
workPeriodic.OnErrorAsync = (id, exception, token) =>
public static Work MakeWork() => new Work(workId, WorkAction)
{
var text = $"work {id}, when {progress}, throw error:{exception.Message}";
Trace.TraceWarning(text);
return Task.CompletedTask;
Timeout = TimeSpan.FromMinutes(20),
OnErrorAsync = (id, exception, token) =>
{
var text = $"work {id}, when {progress}, throw error:{exception.Message}";
Trace.TraceWarning(text);
return Task.CompletedTask;
}
};
return workPeriodic;
}
// TODO: Разделить этот акшн на более мелкие части И использовать telemetryServiceData<..> вместо прямого обращения к БД.
private static async Task WorkAction(string _, IServiceProvider serviceProvider, Action<string, double?> onProgress, CancellationToken token)

View File

@ -513,7 +513,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
if (state.IdState == idStateCreating)
{
var workId = MakeWorkId(idWell);
if (!backgroundWorker.Contains(workId))
if (!backgroundWorker.WorkStore.RunOnceQueue.Any(w => w.Id == workId))
{
var well = (await wellService.GetOrDefaultAsync(idWell, token))!;
var resultFileName = $"Программа бурения {well.Cluster} {well.Caption}.pdf";
@ -540,12 +540,12 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
return Task.CompletedTask;
};
var work = new WorkBase(workId, workAction)
var work = new Work(workId, workAction)
{
OnErrorAsync = onErrorAction
};
backgroundWorker.Push(work);
backgroundWorker.WorkStore.RunOnceQueue.Enqueue(work);
}
}
}
@ -559,7 +559,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
private async Task<int> RemoveDrillingProgramAsync(int idWell, CancellationToken token)
{
var workId = MakeWorkId(idWell);
backgroundWorker.Delete(workId);
backgroundWorker.WorkStore.TryRemoveFromRunOnceQueue(workId);
var filesIds = await context.Files
.Where(f => f.IdWell == idWell &&

View File

@ -52,12 +52,12 @@ namespace AsbCloudInfrastructure.Services.Email
}
var workId = MakeWorkId(notification.IdUser, notification.Title, notification.Message);
if (!backgroundWorker.Contains(workId))
if (!backgroundWorker.WorkStore.RunOnceQueue.Any(w=>w.Id==workId))
{
var workAction = MakeEmailSendWorkAction(notification);
var work = new WorkBase(workId, workAction);
backgroundWorker.Push(work);
var work = new Work(workId, workAction);
backgroundWorker.WorkStore.RunOnceQueue.Enqueue(work);
}
return Task.CompletedTask;

View File

@ -16,16 +16,11 @@ namespace AsbCloudInfrastructure.Services
internal static class LimitingParameterCalcWorkFactory
{
private const string workId = "Limiting parameter calc";
private static readonly TimeSpan workPeriod = TimeSpan.FromMinutes(30);
public static WorkPeriodic MakeWork()
{
var workPeriodic = new WorkPeriodic(workId, WorkAction, workPeriod)
public static Work MakeWork() => new Work(workId, WorkAction)
{
Timeout = TimeSpan.FromMinutes(30)
};
return workPeriodic;
}
// TODO: Разделить этот акшн на более мелкие части И использовать telemetryServiceData<..> вместо прямого обращения к БД.
private static async Task WorkAction(string _, IServiceProvider serviceProvider, Action<string, double?> onProgress, CancellationToken token)

View File

@ -94,8 +94,8 @@ namespace AsbCloudInfrastructure.Services
context.SaveChanges();
};
var work = new WorkBase(workId, workAction);
backgroundWorkerService.Push(work);
var work = new Work(workId, workAction);
backgroundWorkerService.WorkStore.RunOnceQueue.Enqueue(work);
progressHandler.Invoke(new ReportProgressDto
{

View File

@ -48,12 +48,12 @@ namespace AsbCloudInfrastructure.Services.SAUB
instance = new TelemetryDataCache<TDto>();
var worker = provider.GetRequiredService<BackgroundWorker>();
var workId = $"Telemetry cache loading from DB {typeof(TEntity).Name}";
var work = new WorkBase(workId, async (workId, provider, onProgress, token) => {
var work = new Work(workId, async (workId, provider, onProgress, token) => {
var db = provider.GetRequiredService<IAsbCloudDbContext>();
await instance.InitializeCacheFromDBAsync<TEntity>(db, onProgress, token);
});
worker.Push(work);
worker.WorkStore.RunOnceQueue.Enqueue(work);
}
instance.provider = provider;
return instance;
@ -168,7 +168,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
.Where(well => well.IdTelemetry != null)
.ToArrayAsync(token);
var count = wells.Count();
var count = wells.Length;
var i = 0;
foreach (Well well in wells)
{

View File

@ -18,7 +18,6 @@ namespace AsbCloudInfrastructure.Services.Subsystems
internal static class SubsystemOperationTimeCalcWorkFactory
{
private const string workId = "Subsystem operation time calc";
private static readonly TimeSpan workPeriod = TimeSpan.FromMinutes(30);
private const int idSubsytemTorqueMaster = 65537;
private const int idSubsytemSpinMaster = 65536;
@ -26,14 +25,10 @@ namespace AsbCloudInfrastructure.Services.Subsystems
private const int idSubsystemAPDSlide = 12;
private const int idSubsytemMse = 2;
public static WorkPeriodic MakeWork()
public static Work MakeWork() => new Work(workId, WorkAction)
{
var workPeriodic = new WorkPeriodic(workId, WorkAction, workPeriod)
{
Timeout = TimeSpan.FromMinutes(30)
};
return workPeriodic;
}
Timeout = TimeSpan.FromMinutes(20)
};
// TODO: Разделить этот акшн на более мелкие части И использовать telemetryServiceData<..> вместо прямого обращения к БД.
private static async Task WorkAction(string _, IServiceProvider serviceProvider, Action<string, double?> onProgress, CancellationToken token)

View File

@ -29,7 +29,6 @@ namespace AsbCloudInfrastructure.Services
}
private const string workId = "Well statistics update";
private static readonly TimeSpan workPeriod = TimeSpan.FromMinutes(30);
private readonly TelemetryDataCache<TelemetryDataSaubDto> telemetryDataSaubCache;
private readonly TelemetryDataCache<TelemetryDataSpinDto> telemetryDataSpinCache;
@ -53,14 +52,10 @@ namespace AsbCloudInfrastructure.Services
this.gtrRepository = gtrRepository;
}
public static WorkPeriodic MakeWork()
public static Work MakeWork() => new Work(workId, WorkAction)
{
var workPeriodic = new WorkPeriodic(workId, WorkAction, workPeriod)
{
Timeout = TimeSpan.FromMinutes(30)
};
return workPeriodic;
}
Timeout = TimeSpan.FromMinutes(20)
};
private static async Task WorkAction(string workName, IServiceProvider serviceProvider, Action<string, double?> onProgress, CancellationToken token)
{

View File

@ -15,7 +15,6 @@ using AsbCloudInfrastructure.Services.SAUB;
namespace AsbCloudInfrastructure
{
public class Startup
{
public static void BeforeRunHandler(IHost host)
@ -32,11 +31,11 @@ namespace AsbCloudInfrastructure
_ = provider.GetRequiredService<TelemetryDataCache<TelemetryDataSpinDto>>();
var backgroundWorker = provider.GetRequiredService<BackgroundWorker>();
backgroundWorker.Push(WellInfoService.MakeWork());
backgroundWorker.Push(OperationDetectionWorkFactory.MakeWork());
backgroundWorker.Push(SubsystemOperationTimeCalcWorkFactory.MakeWork());
backgroundWorker.Push(LimitingParameterCalcWorkFactory.MakeWork());
backgroundWorker.Push(MakeMemoryMonitoringWork());
backgroundWorker.WorkStore.AddPeriodic(WellInfoService.MakeWork(), TimeSpan.FromMinutes(30));
backgroundWorker.WorkStore.AddPeriodic(OperationDetectionWorkFactory.MakeWork(), TimeSpan.FromMinutes(15));
backgroundWorker.WorkStore.AddPeriodic(SubsystemOperationTimeCalcWorkFactory.MakeWork(), TimeSpan.FromMinutes(30));
backgroundWorker.WorkStore.AddPeriodic(LimitingParameterCalcWorkFactory.MakeWork(), TimeSpan.FromMinutes(30));
backgroundWorker.WorkStore.AddPeriodic(MakeMemoryMonitoringWork(), TimeSpan.FromMinutes(1));
var notificationBackgroundWorker = provider.GetRequiredService<NotificationBackgroundWorker>();
@ -48,17 +47,15 @@ namespace AsbCloudInfrastructure
});
}
static WorkPeriodic MakeMemoryMonitoringWork()
static Work MakeMemoryMonitoringWork()
{
var workId = "Memory monitoring";
var workAction = (string _, IServiceProvider _, Action<string, double?> _, CancellationToken _) => {
var bytes = GC.GetTotalMemory(false);
var bytesString = FromatBytes(bytes);
System.Diagnostics.Trace.TraceInformation($"Total memory allocated is {bytesString} bytes. DbContext count is:{AsbCloudDbContext.ReferenceCount}");
return Task.CompletedTask;
};
var workPeriod = TimeSpan.FromMinutes(1);
var work = new WorkPeriodic(workId, workAction, workPeriod);
var work = new Work("Memory monitoring", workAction);
return work;
}

View File

@ -366,7 +366,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
var state = await service.GetStateAsync(idWell, publisher1.Id, CancellationToken.None);
Assert.Equal(2, state.IdState);
backgroundWorkerMock.Verify(s => s.Push(It.IsAny<WorkBase>()));
backgroundWorkerMock.Verify(s => s.Push(It.IsAny<Work>()));
}
[Fact]

View File

@ -29,12 +29,12 @@ public class SignalRNotificationTransportService : INotificationTransportService
{
var workId = HashCode.Combine(notifications.Select(n => n.Id)).ToString("x");
if (backgroundWorker.Contains(workId))
if (backgroundWorker.WorkStore.RunOnceQueue.Any(w => w.Id == workId))
return Task.CompletedTask;
var workAction = MakeSignalRSendWorkAction(notifications);
var work = new WorkBase(workId, workAction);
backgroundWorker.Push(work);
var work = new Work(workId, workAction);
backgroundWorker.WorkStore.RunOnceQueue.Enqueue(work);
return Task.CompletedTask;
}