forked from ddrilling/AsbCloudServer
143 lines
4.9 KiB
C#
143 lines
4.9 KiB
C#
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);
|
||
}
|