using AsbCloudApp.Data; using System; using System.Diagnostics; using System.Text; using System.Threading; using System.Threading.Tasks; namespace AsbCloudInfrastructure.Background; /// /// Класс разовой работы. /// Разовая работа приоритетнее периодической. /// public abstract class Work : BackgroundWorkDto { private CancellationTokenSource? stoppingCts; private sealed class WorkBase : Work { private Func, CancellationToken, Task> ActionAsync { get; } public WorkBase(string id, Func, CancellationToken, Task> actionAsync) : base(id) { ActionAsync = actionAsync; } protected override Task Action(string id, IServiceProvider services, Action onProgressCallback, CancellationToken token) => ActionAsync(id, services, onProgressCallback, token); } /// /// Делегат обработки ошибки. /// Не должен выполняться долго. /// public Func? OnErrorAsync { get; set; } /// /// макс продолжительность обработки исключения /// public TimeSpan OnErrorHandlerTimeout { get; set; } = TimeSpan.FromSeconds(5); /// /// Базовая работа /// /// public Work(string id) { Id = id; } /// /// Создать работу на основе делегата /// /// /// /// [Obsolete("Use implement Work class")] public static Work CreateByDelegate(string id, Func, CancellationToken, Task> actionAsync) { return new WorkBase(id, actionAsync); } /// /// Запустить работу /// /// /// /// True - success, False = fail public async Task Start(IServiceProvider services, CancellationToken token) { SetStatusStart(); try { stoppingCts = CancellationTokenSource.CreateLinkedTokenSource(token); var task = Action(Id, services, UpdateStatus, stoppingCts.Token); await task.WaitAsync(Timeout, stoppingCts.Token); SetStatusComplete(); return true; } catch (Exception exception) { var message = FormatExceptionMessage(exception); SetLastError(message); if (OnErrorAsync is not null) { try { var task = Task.Run( async () => await OnErrorAsync(Id, exception, token), token); await task.WaitAsync(OnErrorHandlerTimeout, token); } catch (Exception onErrorAsyncException) { var message2 = FormatExceptionMessage(onErrorAsyncException); Trace.TraceError($"Backgroud work:\"{Id}\" OnError handler throws exception: {message2}"); } } } return false; } public void Stop() { stoppingCts?.Cancel(); } private static string FormatExceptionMessage(Exception exception) { var firstException = FirstException(exception); var message = new StringBuilder(); if (firstException != exception) { message.Append("top exception:"); message.AppendLine(exception.Message); message.Append("inner exception:"); message.AppendLine(firstException.Message); } else message.AppendLine(firstException.Message); message.AppendLine(exception.StackTrace); return message.ToString(); } private static Exception FirstException(Exception exception) { if (exception.InnerException is not null) return FirstException(exception.InnerException); return exception; } /// /// делегат фоновой работы /// /// Идентификатор работы /// Поставщик сервисов /// on progress callback. String - new state text. double? - optional progress 0-100% /// /// protected abstract Task Action(string id, IServiceProvider services, Action onProgressCallback, CancellationToken token); }