From fefcfe3a9978b1ef3c6f0394cc46224ddd0395a2 Mon Sep 17 00:00:00 2001 From: Andrey Ovsiankin Date: Wed, 17 Mar 2021 16:39:57 +0300 Subject: [PATCH] =?UTF-8?q?API=20=D1=84=D0=BE=D0=BD=D0=BE=D0=B2=D1=8B?= =?UTF-8?q?=D1=85=20=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B9=20=D0=B8=20?= =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82=D1=8B.=20closes=20#764,=20closes=20#48?= =?UTF-8?q?9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Library/Tasks/BackgroundTask.cs | 33 ++- .../Library/Tasks/BackgroundTasksManager.cs | 143 ++++++++++-- .../{TaskStatusEnum.cs => TaskStateEnum.cs} | 8 +- .../Contexts/CriticalSectionContext.cs | 4 +- src/ScriptEngine/Machine/MachineInstance.cs | 3 +- tests/tasks.os | 206 ++++++++++++++++++ 6 files changed, 358 insertions(+), 39 deletions(-) rename src/ScriptEngine.HostedScript/Library/Tasks/{TaskStatusEnum.cs => TaskStateEnum.cs} (71%) create mode 100644 tests/tasks.os diff --git a/src/ScriptEngine.HostedScript/Library/Tasks/BackgroundTask.cs b/src/ScriptEngine.HostedScript/Library/Tasks/BackgroundTask.cs index f10d4140b..5b670910f 100644 --- a/src/ScriptEngine.HostedScript/Library/Tasks/BackgroundTask.cs +++ b/src/ScriptEngine.HostedScript/Library/Tasks/BackgroundTask.cs @@ -6,6 +6,7 @@ This Source Code Form is subject to the terms of the ----------------------------------------------------------*/ using System.Linq; +using System.Threading; using System.Threading.Tasks; using ScriptEngine.Machine; using ScriptEngine.Machine.Contexts; @@ -22,7 +23,6 @@ public BackgroundTask(IRuntimeContextInstance target, string methodName, ArrayIm { Target = target; MethodName = methodName; - Status = TaskStatusEnum.NotRunned; if(parameters != default) Parameters = new ArrayImpl(parameters); @@ -34,7 +34,7 @@ public BackgroundTask(IRuntimeContextInstance target, string methodName, ArrayIm public Task WorkerTask { get; set; } - [ContextProperty("Идентификатор","Identifier")] + [ContextProperty("УникальныйИдентификатор","UUID")] public GuidWrapper Identifier { get; private set; } [ContextProperty("ИмяМетода","MethodName")] @@ -42,12 +42,12 @@ public BackgroundTask(IRuntimeContextInstance target, string methodName, ArrayIm [ContextProperty("Объект","Object")] public IRuntimeContextInstance Target { get; private set; } - - [ContextProperty("Состояние", "Status")] - public TaskStatusEnum Status { get; private set; } - + + [ContextProperty("Состояние", "State")] + public TaskStateEnum State { get; private set; } + [ContextProperty("Параметры", "Parameters")] - public ArrayImpl Parameters { get; private set; } + public IValue Parameters { get; private set; } = ValueFactory.Create(); [ContextProperty("Результат", "Result")] public IValue Result { get; private set; } = ValueFactory.Create(); @@ -63,24 +63,23 @@ public BackgroundTask(IRuntimeContextInstance target, string methodName, ArrayIm [ContextMethod("ОжидатьЗавершения", "Wait")] public bool Wait(int timeout = 0) { - if(timeout < 0) - throw RuntimeException.InvalidArgumentValue(); - if (timeout == 0) - timeout = -1; + timeout = BackgroundTasksManager.ConvertTimeout(timeout); return WorkerTask.Wait(timeout); } public void ExecuteOnCurrentThread() { - if (Status != TaskStatusEnum.NotRunned) + if (State != TaskStateEnum.NotRunned) throw new RuntimeException(Locale.NStr("ru = 'Неверное состояние задачи';en = 'Incorrect task status'")); - - var parameters = Parameters?.ToArray() ?? new IValue[0]; + + var parameters = Parameters is ArrayImpl array ? + array.ToArray() : + new IValue[0]; try { - Status = TaskStatusEnum.Running; + State = TaskStateEnum.Running; if (_method.IsFunction) { Target.CallAsFunction(_methIndex, parameters, out var result); @@ -91,11 +90,11 @@ public void ExecuteOnCurrentThread() Target.CallAsProcedure(_methIndex, parameters); } - Status = TaskStatusEnum.Completed; + State = TaskStateEnum.Completed; } catch (RuntimeException exception) { - Status = TaskStatusEnum.CompletedWithErrors; + State = TaskStateEnum.CompletedWithErrors; ExceptionInfo = new ExceptionInfoContext(exception); } } diff --git a/src/ScriptEngine.HostedScript/Library/Tasks/BackgroundTasksManager.cs b/src/ScriptEngine.HostedScript/Library/Tasks/BackgroundTasksManager.cs index 4cfd1c5d8..3670edf51 100644 --- a/src/ScriptEngine.HostedScript/Library/Tasks/BackgroundTasksManager.cs +++ b/src/ScriptEngine.HostedScript/Library/Tasks/BackgroundTasksManager.cs @@ -8,6 +8,7 @@ This Source Code Form is subject to the terms of the using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; using ScriptEngine.Machine; @@ -33,8 +34,8 @@ public BackgroundTasksManager(ScriptingEngine engine) /// Имя экспортного метода в объекте /// Массив параметров метода /// ФоновоеЗадание - [ContextMethod("Создать", "Create")] - public BackgroundTask Create(IRuntimeContextInstance target, string methodName, ArrayImpl parameters = null) + [ContextMethod("Выполнить", "Execute")] + public BackgroundTask Execute(IRuntimeContextInstance target, string methodName, ArrayImpl parameters = null) { var task = new BackgroundTask(target, methodName, parameters); _tasks.Add(task); @@ -61,30 +62,144 @@ public void Clear() } /// - /// Ждать завершения всех незавершенных заданий + /// Ожидает завершения всех переданных заданий /// - [ContextMethod("ЖдатьВсе", "WaitAll")] - public void WaitAll() + /// Массив заданий + /// Таймаут ожидания. 0 = ожидать бесконечно + /// Истина - дождались все задания, Ложь - истек таймаут + [ContextMethod("ОжидатьВсе", "WaitAll")] + public bool WaitAll(ArrayImpl tasks, int timeout = 0) { - var tasks = _tasks - .Select(x => x.WorkerTask) - .ToArray(); - + var workers = GetWorkerTasks(tasks); + timeout = ConvertTimeout(timeout); + // Фоновые задания перехватывают исключения внутри себя // и выставляют свойство ИнформацияОбОшибке // если WaitAll выбросит исключение, значит действительно что-то пошло не так на уровне самого Task - Task.WaitAll(tasks); + return Task.WaitAll(workers, timeout); + } + + /// + /// Ожидать хотя бы одно из переданных заданий. + /// + /// Массив заданий + /// Таймаут ожидания. 0 = ожидать бесконечно + /// Число. Индекс в массиве заданий, указывающий на элемент-задание, которое завершилось. -1 = сработал таймаут + [ContextMethod("ОжидатьЛюбое", "WaitAny")] + public int WaitAny(ArrayImpl tasks, int timeout = 0) + { + var workers = GetWorkerTasks(tasks); + timeout = ConvertTimeout(timeout); + + // Фоновые задания перехватывают исключения внутри себя + // и выставляют свойство ИнформацияОбОшибке + // если WaitAny выбросит исключение, значит действительно что-то пошло не так на уровне самого Task + return Task.WaitAny(workers, timeout); + } + + /// + /// Блокирует поток до завершения всех заданий. + /// Выбрасывает исключение, если какие-то задания завершились аварийно. + /// Выброшенное исключение в свойстве Параметры содержит массив аварийных заданий. + /// + [ContextMethod("ОжидатьЗавершенияЗадач", "WaitCompletionOfTasks")] + public void WaitCompletionOfTasks() + { + lock (_tasks) + { + var workers = GetWorkerTasks(); + Task.WaitAll(workers); + + var failedTasks = _tasks.Where(x => x.State == TaskStateEnum.CompletedWithErrors) + .ToList(); + + if (failedTasks.Any()) + { + throw new ParametrizedRuntimeException( + Locale.NStr("ru = 'Задания завершились с ошибками';en = 'Tasks are completed with errors'"), + new ArrayImpl(failedTasks)); + } + + _tasks.Clear(); + } + } + + [ContextMethod("ПолучитьФоновыеЗадания", "GetBackgroundJobs")] + public ArrayImpl GetBackgroundJobs(StructureImpl filter = default) + { + if(filter == default) + return new ArrayImpl(_tasks.ToArray()); + + var arr = new ArrayImpl(); + foreach (var task in _tasks) + { + var result = true; + foreach (var filterItem in filter) + { + switch (filterItem.Key.AsString().ToLower()) + { + case "состояние": + case "state": + var enumval = filterItem.Value as CLREnumValueWrapper; + if(enumval == default) + continue; + + result = result && task.State == enumval.UnderlyingValue; + break; + + case "имяметода": + case "methodname": + result = result && task.MethodName.ToLower() == filterItem.Value.AsString(); + break; + + case "объект": + case "object": + result = result && task.Target.Equals(filterItem.Value); + break; + + case "уникальныйидентификатор": + case "uuid": + result = result && task.Identifier.Equals(filterItem.Value); + break; + } + } + + if(result) + arr.Add(task); + } + + return arr; + } + + internal static int ConvertTimeout(int timeout) + { + if(timeout < 0) + throw RuntimeException.InvalidArgumentValue(); + + return timeout == 0 ? Timeout.Infinite : timeout; + } + + private static Task[] GetWorkerTasks(ArrayImpl tasks) + { + return tasks + .Cast() + .Select(x => x.WorkerTask) + .ToArray(); + } + + private static Task[] GetWorkerTasks(IEnumerable tasks) + { + return tasks.Select(x => x.WorkerTask).ToArray(); } - [ContextMethod("Получить", "Get")] - public ArrayImpl Get() + private Task[] GetWorkerTasks() { - return new ArrayImpl(_tasks.ToArray()); + return GetWorkerTasks(_tasks); } public void Dispose() { - WaitAll(); + Task.WaitAll(GetWorkerTasks()); _tasks.Clear(); } } diff --git a/src/ScriptEngine.HostedScript/Library/Tasks/TaskStatusEnum.cs b/src/ScriptEngine.HostedScript/Library/Tasks/TaskStateEnum.cs similarity index 71% rename from src/ScriptEngine.HostedScript/Library/Tasks/TaskStatusEnum.cs rename to src/ScriptEngine.HostedScript/Library/Tasks/TaskStateEnum.cs index 19583c971..783bf2e6a 100644 --- a/src/ScriptEngine.HostedScript/Library/Tasks/TaskStatusEnum.cs +++ b/src/ScriptEngine.HostedScript/Library/Tasks/TaskStateEnum.cs @@ -9,16 +9,16 @@ This Source Code Form is subject to the terms of the namespace ScriptEngine.HostedScript.Library.Tasks { - [EnumerationType("СостояниеЗадания", "TaskStatus")] - public enum TaskStatusEnum + [EnumerationType("СостояниеФоновогоЗадания", "BackgroundJobState")] + public enum TaskStateEnum { [EnumItem("НеВыполнялось", "NotRunned")] NotRunned, - [EnumItem("Выполняется", "Running")] + [EnumItem("Активно", "Active")] Running, [EnumItem("Завершено", "Completed")] Completed, - [EnumItem("ЗавершеноСОшибками", "CompletedWithErrors")] + [EnumItem("ЗавершеноАварийно", "Failed")] CompletedWithErrors } } \ No newline at end of file diff --git a/src/ScriptEngine/Machine/Contexts/CriticalSectionContext.cs b/src/ScriptEngine/Machine/Contexts/CriticalSectionContext.cs index b5dedf61d..009f78bcb 100644 --- a/src/ScriptEngine/Machine/Contexts/CriticalSectionContext.cs +++ b/src/ScriptEngine/Machine/Contexts/CriticalSectionContext.cs @@ -49,13 +49,13 @@ public void Dispose() } [ScriptConstructor] - public CriticalSectionContext Create() + public static CriticalSectionContext Create() { return new CriticalSectionContext(); } [ScriptConstructor(Name = "По объекту")] - public CriticalSectionContext Create(IValue instance) + public static CriticalSectionContext Create(IValue instance) { return new CriticalSectionContext(instance.AsObject()); } diff --git a/src/ScriptEngine/Machine/MachineInstance.cs b/src/ScriptEngine/Machine/MachineInstance.cs index 0e05de6d3..05e1b1e7e 100644 --- a/src/ScriptEngine/Machine/MachineInstance.cs +++ b/src/ScriptEngine/Machine/MachineInstance.cs @@ -1360,9 +1360,8 @@ private void RaiseException(int arg) else { var exceptionValue = _operationStack.Pop().GetRawValue(); - if (exceptionValue is ExceptionTemplate) + if (exceptionValue is ExceptionTemplate excInfo) { - var excInfo = exceptionValue as ExceptionTemplate; throw new ParametrizedRuntimeException(excInfo.Message, excInfo.Parameter); } else diff --git a/tests/tasks.os b/tests/tasks.os new file mode 100644 index 000000000..5b86c99c8 --- /dev/null +++ b/tests/tasks.os @@ -0,0 +1,206 @@ +Перем юТест; + +Перем глБлокировка; +Перем глСумма; + +//////////////////////////////////////////////////////////////////// +// Программный интерфейс + +Функция ПолучитьСписокТестов(ЮнитТестирование) Экспорт + + юТест = ЮнитТестирование; + + ВсеТесты = Новый Массив; + + ВсеТесты.Добавить("ТестДолжен_ПроверитьСозданиеЗаданий"); + ВсеТесты.Добавить("ТестДолжен_ПроверитьПоискЗаданий"); + ВсеТесты.Добавить("ТестДолжен_ПроверитьОжиданиеВсехЗаданий"); + ВсеТесты.Добавить("ТестДолжен_ПроверитьОжиданиеЛюбогоИзЗаданий"); + ВсеТесты.Добавить("ТестДолжен_ПроверитьОжиданиеКонкретногоЗадания"); + ВсеТесты.Добавить("ТестДолжен_ПроверитьЧтоВЗаданииПроставленаИнформацияОбОшибке"); + ВсеТесты.Добавить("ТестДолжен_ПроверитьЧтоВозвращаетсяРезультат"); + ВсеТесты.Добавить("ТестДолжен_ПроверитьЧтоРаботаетБлокировка"); + + Возврат ВсеТесты; + +КонецФункции + +Процедура ПередЗапускомТеста() Экспорт + ФоновыеЗадания.Очистить(); +КонецПроцедуры + +Процедура ПроцедураБезПараметров() Экспорт + Приостановить(500); +КонецПроцедуры + +Процедура ПроцедураСПараметрами(Интервал) Экспорт + Приостановить(Интервал); +КонецПроцедуры + +Процедура ПроцедураСИсключением() Экспорт + Приостановить(500); + ВызватьИсключение "Я ошибка в коде"; +КонецПроцедуры + +Функция ПодсчетСуммы(Знач Начало, Знач Конец) Экспорт + + //дадим всем заданиям шанс начаться + Приостановить(500); + + СуммаНаОтрезке = 0; + Для Сч = Начало По Конец Цикл + СуммаНаОтрезке = СуммаНаОтрезке + Сч; + КонецЦикла; + + Возврат СуммаНаОтрезке; + +КонецФункции + +Процедура ПодсчетГлобальнойСуммы(Знач Начало, Знач Конец) Экспорт + + СуммаНаОтрезке = ПодсчетСуммы(Начало, Конец); + + глБлокировка.Заблокировать(); + глСумма = глСумма + СуммаНаОтрезке; + глБлокировка.Разблокировать(); + +КонецПроцедуры + +Процедура ТестДолжен_ПроверитьСозданиеЗаданий() Экспорт + + Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "ПроцедураБезПараметров"); + юТест.ПроверитьИстину(ЗначениеЗаполнено(Задание.УникальныйИдентификатор)); + юТест.ПроверитьРавенство("ПроцедураБезПараметров", Задание.ИмяМетода); + юТест.ПроверитьРавенство(ЭтотОбъект, Задание.Объект); + юТест.ПроверитьРавенство(Неопределено, Задание.Параметры); + + Задание.ОжидатьЗавершения(); + + юТест.ПроверитьРавенство(СостояниеФоновогоЗадания.Завершено, Задание.Состояние); + +КонецПроцедуры + +Процедура ТестДолжен_ПроверитьПоискЗаданий() Экспорт + + З = ФоновыеЗадания.Выполнить(ЭтотОбъект, "ПроцедураБезПараметров"); + ФоновыеЗадания.Выполнить(ЭтотОбъект, "ПроцедураБезПараметров"); + ФоновыеЗадания.Выполнить(ЭтотОбъект, "ПроцедураБезПараметров"); + ФоновыеЗадания.Выполнить(ЭтотОбъект, "ПроцедураБезПараметров"); + + Все = ФоновыеЗадания.ПолучитьФоновыеЗадания(); + юТест.ПроверитьРавенство(4, Все.Количество()); + + ПоИд = ФоновыеЗадания.ПолучитьФоновыеЗадания(Новый Структура("UUID", З.УникальныйИдентификатор)); + юТест.ПроверитьРавенство(1, ПоИд.Количество()); + +КонецПроцедуры + +Процедура ТестДолжен_ПроверитьОжиданиеВсехЗаданий() Экспорт + + МассивПараметров = Новый Массив; + МассивПараметров.Добавить(3000); + + МассивЗаданий = Новый Массив; + + МассивЗаданий.Добавить(ФоновыеЗадания.Выполнить(ЭтотОбъект, "ПроцедураСПараметрами", МассивПараметров)); + МассивЗаданий.Добавить(ФоновыеЗадания.Выполнить(ЭтотОбъект, "ПроцедураБезПараметров")); + + ФоновыеЗадания.ОжидатьВсе(МассивЗаданий); + + Завершенные = ФоновыеЗадания.ПолучитьФоновыеЗадания(Новый Структура("Состояние", СостояниеФоновогоЗадания.Завершено)); + юТест.ПроверитьРавенство(2, Завершенные.Количество()); + +КонецПроцедуры + +Процедура ТестДолжен_ПроверитьОжиданиеЛюбогоИзЗаданий() Экспорт + + МассивПараметров = Новый Массив; + МассивПараметров.Добавить(3000); + + МассивЗаданий = Новый Массив; + + МассивЗаданий.Добавить(ФоновыеЗадания.Выполнить(ЭтотОбъект, "ПроцедураСПараметрами", МассивПараметров)); + МассивЗаданий.Добавить(ФоновыеЗадания.Выполнить(ЭтотОбъект, "ПроцедураБезПараметров")); + + ФоновыеЗадания.ОжидатьЛюбое(МассивЗаданий); + + Завершенные = ФоновыеЗадания.ПолучитьФоновыеЗадания(Новый Структура("Состояние", СостояниеФоновогоЗадания.Завершено)); + юТест.ПроверитьМеньшеИлиРавно(Завершенные.Количество(), 2); + +КонецПроцедуры + +Процедура ТестДолжен_ПроверитьОжиданиеКонкретногоЗадания() Экспорт + + Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "ПроцедураБезПараметров"); + Задание.ОжидатьЗавершения(); + + юТест.ПроверитьРавенство(СостояниеФоновогоЗадания.Завершено, Задание.Состояние); + +КонецПроцедуры + +Процедура ТестДолжен_ПроверитьЧтоВЗаданииПроставленаИнформацияОбОшибке() Экспорт + + Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "ПроцедураСИсключением"); + Задание.ОжидатьЗавершения(); + + юТест.ПроверитьРавенство(СостояниеФоновогоЗадания.ЗавершеноАварийно, Задание.Состояние); + юТест.ПроверитьРавенство("Я ошибка в коде", Задание.ИнформацияОбОшибке.Описание); + +КонецПроцедуры + +Процедура ТестДолжен_ПроверитьЧтоВозвращаетсяРезультат() Экспорт + + МассивПараметров = Новый Массив(2); + МассивПараметров[0] = 1; + МассивПараметров[1] = 100; + + ФоновыеЗадания.Выполнить(ЭтотОбъект, "ПодсчетСуммы", МассивПараметров); + + МассивПараметров[0] = 101; + МассивПараметров[1] = 200; + + ФоновыеЗадания.Выполнить(ЭтотОбъект, "ПодсчетСуммы", МассивПараметров); + + МассивПараметров[0] = 201; + МассивПараметров[1] = 300; + + ФоновыеЗадания.Выполнить(ЭтотОбъект, "ПодсчетСуммы", МассивПараметров); + + ВсеЗадания = ФоновыеЗадания.ПолучитьФоновыеЗадания(); + ФоновыеЗадания.ОжидатьВсе(ВсеЗадания); + + Сумма = 0; + Для Каждого Задание Из ВсеЗадания Цикл + Сумма = Сумма + Задание.Результат; + КонецЦикла; + + юТест.ПроверитьРавенство(45150, Сумма); + +КонецПроцедуры + +Процедура ТестДолжен_ПроверитьЧтоРаботаетБлокировка() Экспорт + + глБлокировка = Новый БлокировкаРесурса; + глСумма = 0; + + МассивПараметров = Новый Массив(2); + МассивПараметров[0] = 1; + МассивПараметров[1] = 1000; + + ФоновыеЗадания.Выполнить(ЭтотОбъект, "ПодсчетГлобальнойСуммы", МассивПараметров); + + МассивПараметров[0] = 1001; + МассивПараметров[1] = 2000; + + ФоновыеЗадания.Выполнить(ЭтотОбъект, "ПодсчетГлобальнойСуммы", МассивПараметров); + + МассивПараметров[0] = 2001; + МассивПараметров[1] = 3000; + + ФоновыеЗадания.Выполнить(ЭтотОбъект, "ПодсчетГлобальнойСуммы", МассивПараметров); + + ФоновыеЗадания.ОжидатьЗавершенияЗадач(); + + юТест.ПроверитьРавенство(4501500, глСумма); + +КонецПроцедуры