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, глСумма);
+
+КонецПроцедуры