using AsbCloudApp.Data; using System; using System.Diagnostics; using System.Text; using System.Threading; using System.Threading.Tasks; namespace AsbCloudInfrastructure.Background; /// <summary> /// Класс разовой работы. /// Разовая работа приоритетнее периодической. /// </summary> public abstract class Work : BackgroundWorkDto { private CancellationTokenSource? stoppingCts; private sealed class WorkBase : Work { private Func<string, IServiceProvider, Action<string, double?>, CancellationToken, Task> ActionAsync { get; } public WorkBase(string id, Func<string, IServiceProvider, Action<string, double?>, CancellationToken, Task> actionAsync) : base(id) { ActionAsync = actionAsync; } protected override Task Action(string id, IServiceProvider services, Action<string, double?> onProgressCallback, CancellationToken token) => ActionAsync(id, services, onProgressCallback, token); } /// <summary> /// Делегат обработки ошибки. /// Не должен выполняться долго. /// </summary> public Func<string, Exception, CancellationToken, Task>? OnErrorAsync { get; set; } /// <summary> /// макс продолжительность обработки исключения /// </summary> public TimeSpan OnErrorHandlerTimeout { get; set; } = TimeSpan.FromSeconds(5); /// <summary> /// Базовая работа /// </summary> /// <param name="id"></param> public Work(string id) { Id = id; } /// <summary> /// Создать работу на основе делегата /// </summary> /// <param name="id"></param> /// <param name="actionAsync"></param> /// <returns></returns> [Obsolete("Use implement Work class")] public static Work CreateByDelegate(string id, Func<string, IServiceProvider, Action<string, double?>, CancellationToken, Task> actionAsync) { return new WorkBase(id, actionAsync); } /// <summary> /// Запустить работу /// </summary> /// <param name="services"></param> /// <param name="token"></param> /// <returns>True - success, False = fail</returns> public async Task<bool> 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; } /// <summary> /// делегат фоновой работы /// </summary> /// <param name="id">Идентификатор работы</param> /// <param name="services">Поставщик сервисов</param> /// <param name="onProgressCallback">on progress callback. String - new state text. double? - optional progress 0-100%</param> /// <param name="token"></param> /// <returns></returns> protected abstract Task Action(string id, IServiceProvider services, Action<string, double?> onProgressCallback, CancellationToken token); }