Асинхронный вызов скриптов
- “Выстрелил и забыл” (fire and forget)
- Ожидание завершения всех потоков
- Ожидание завершения любого потока
- Возврат значения из фонового задания
Внешний или подключаемый скрипт можно вызвать асинхронно при помощи опций TASK или WORK команды EXECUTE. В том и другом случае скрипт будет выполнен асинхронно в другом потоке операционной системы, а не в текущем потоке вызова. Разница между двумя этими опциями заключается в том, что в первом случае поток выполнения “арендуется” из пула потоков .NET, а во втором - создаётся отдельный поток операционной системы. Опцию TASK рекомендуется использовать для коротких небольших заданий, а WORK наоборот для долгих, возможно “вечных”, процессов.
Полный синтаксис асинхронной команды EXECUTE выглядит следующим образом:
EXECUTE [{ TASK | WORK }] 'file://<async-script>.djs'
[DEFAULT 'file://<default-script>.djs']
[AS <name>]
[WITH <parameters>]
[INTO <array-of-tasks>]
Команда EXECUTE создаёт и добавляет в переменную array
объекты task
. Такой массив необходимо заранее объявить именно для этих целей в шапке вызывающего родительского скрипта. Ссылка на этот массив указывется в необязательном предложении INTO команды EXECUTE. Добавление task
в array
выполняется после запуска дочернего потока или помещения задания в очередь на выполнение пулом потоков .NET. Доступ к массиву заданий осуществляется обычным для типа array
способом. Элементами массива являются объекты, которые имеют следующую структуру:
Свойство | Тип данных |
Описание |
---|---|---|
Id | number | Системный идентификатор потока |
Name | string | Пользовательское имя задания, которое может быть выражением. Определяется при помощи опции AS команды EXECUTE. |
Status | string | Текущее состояние потока |
Result | любое значение |
Результат выполнения задания |
IsFaulted | boolean | Признак завершения потока из-за ошибки |
IsCanceled | boolean | Признак завершения потока системой |
IsCompleted | boolean | Признак завершения выполнения потока |
IsSucceeded | boolean | Признак завершения потока без ошибок |
Таблица состояний потока (свойство “Status”)
Значение | Описание |
---|---|
Created | Задание создано, но ещё не запланировано к выполнению системой |
WaitingToRun | Задание запланировано и ожидает начала выполнения |
Running | Задание выполняется |
WaitingForChildrenToComplete | Задание выполнено и неявно ожидает завершения, подключенных дочерних задач |
RanToCompletion | Задание выполнено без ошибок |
Canceled | Выполнение задания прервано системой по сигналу немедленного завершения |
Faulted | Задание завершилось из-за необработанной ошибки |
Для синхронизации выполнения, созданных таким образом дочерних потоков, используется команда WAIT. Эта команда синхронно ожидает завершения всех или одного любого потока из указанного массива заданий (опции ALL и ANY соответственно). На время своего ожидания команда WAIT блокирует выполнение текущего родительского потока. Опция ANY требует обязательного использования передложения INTO, где указывается ссылка на переменную, в которую будет возвращено значение завершившегося задания. Такую переменную необходимо объявить заранее именно для этих целей в шапке вызывающего родительского скрипта.
Полный синтаксис команды WAIT выглядит следующим образом:
WAIT [{ ALL | ANY }] <array-of-tasks> [INTO <result>] [TIMEOUT <seconds>]
- <array-of-tasks> - массив заданий, завершение которых ожидает команда.
- <seconds> - период ожидания завершения заданий в секундах. По истечению этого периода времени возникает тайм-аут и выполнение команды прекращается. Данная опция необязательна, в таком случае команда может ожидать завершения заданий или задания бесконечно.
- <result> - результат ожидания выполнения заданий:
- ALL - значение типа
boolean
:TRUE
- все задания выполнились до завершения тайм-аута.FALSE
- до завершения всех заданий возник тайм-аут.
- ANY - значение типа
task
илиNULL
:NULL
- ни одно задание не завершилось до наступления тайм-аута.task
- задание, которое завершилось до наступления тайм-аута.
- ALL - значение типа
Команда WAIT получает и помещает результат выполнения каждого дочернего потока в свойство “Result” соответствующего объекта task
. Внешний скрипт может возвращать результат своей работы вызывающему родительскому скрипту при помощи команды RETURN. Если эта команда не используется, то в свойство “Result” возвращается значение NULL
. Кроме этого команда WAIT выполняет перехват всех, необработанных дочерними скриптами, ошибок. Описание ошибки сохраняется в том же самом свойстве “Result” как текст (тип данных string
). Результат выполнения доступен только после полного завершения работы потока через соответствующий ему объект task
.
Если команда WAIT не используется, то выполнение дочерних потоков продолжается даже тогда, когда родительский скрипт уже завершился и его поток уничтожен операционной системой. Таким образом время жизни дочерних потоков совпадает со временем жизни хоста DaJet Script, то есть процессом операционной системы, в котором работает такой хост.
“Выстрелил и забыл” (fire and forget)
Самый простой способ асинхронного вызова “без обязательств”. Основным недостатком такого подхода является невозможность родительскому скрипту узнать об успешности или неуспешности выполнения дочернего скрипта.
*******************************
* Дочерние скрипты task-n.djs *
*******************************
PRINT '[TASK N] НАЧАЛО'
SLEEP 3 -- Имитируем работу
PRINT '[TASK N] КОНЕЦ'
*******************************************
* Родительский скрипт fire-and-forget.djs *
*******************************************
PRINT '[MAIN] НАЧАЛО'
EXECUTE TASK 'file://task-1.djs'
EXECUTE TASK 'file://task-2.djs'
EXECUTE TASK 'file://task-3.djs'
PRINT '[MAIN] КОНЕЦ'
Результат выполнения скрипта fire-and-forget.djs
[2025-02-15 19:37:29] [MAIN] НАЧАЛО
[2025-02-15 19:37:29] [TASK 2] НАЧАЛО
[2025-02-15 19:37:29] [MAIN] КОНЕЦ
[2025-02-15 19:37:29] [TASK 1] НАЧАЛО
[2025-02-15 19:37:29] [TASK 3] НАЧАЛО
[2025-02-15 19:37:32] [TASK 3] КОНЕЦ
[2025-02-15 19:37:32] [TASK 1] КОНЕЦ
[2025-02-15 19:37:32] [TASK 2] КОНЕЦ
Ожидание завершения всех потоков
Дочерние скрипты имеют такой же код как в предыдущем примере “Выстрелил и забыл”.
****************************************
* Родительский скрипт wait-for-all.djs *
****************************************
DECLARE @task object
DECLARE @tasks array
PRINT '[MAIN] НАЧАЛО'
EXECUTE TASK 'file://task-1.djs' INTO @tasks
EXECUTE TASK 'file://task-2.djs' INTO @tasks
EXECUTE TASK 'file://task-3.djs' INTO @tasks
PRINT '[MAIN] WAIT ALL'
WAIT ALL @tasks
FOR @task IN @tasks
PRINT '***'
PRINT 'Id = ' + @task.Id
PRINT 'Status = ' + @task.Status
PRINT 'IsFaulted = ' + @task.IsFaulted
PRINT 'IsCanceled = ' + @task.IsCanceled
PRINT 'IsCompleted = ' + @task.IsCompleted
PRINT 'IsSucceeded = ' + @task.IsSucceeded
PRINT 'Result = ' + @task.Result
END
PRINT '[MAIN] КОНЕЦ'
Результат выполнения скрипта wait-for-all.djs
[2025-02-15 20:23:07] [MAIN] НАЧАЛО
[2025-02-15 20:23:07] [TASK 1] НАЧАЛО
[2025-02-15 20:23:07] [MAIN] WAIT ALL
[2025-02-15 20:23:07] [TASK 2] НАЧАЛО
[2025-02-15 20:23:07] [TASK 3] НАЧАЛО
[2025-02-15 20:23:10] [TASK 3] КОНЕЦ
[2025-02-15 20:23:10] [TASK 1] КОНЕЦ
[2025-02-15 20:23:10] [TASK 2] КОНЕЦ
[2025-02-15 20:23:10] ***
[2025-02-15 20:23:10] Id = 1
[2025-02-15 20:23:10] Status = RanToCompletion
[2025-02-15 20:23:10] IsFaulted = false
[2025-02-15 20:23:10] IsCanceled = false
[2025-02-15 20:23:10] IsCompleted = true
[2025-02-15 20:23:10] IsSucceeded = true
[2025-02-15 20:23:10] Result = null
[2025-02-15 20:23:10] ***
[2025-02-15 20:23:10] Id = 2
[2025-02-15 20:23:10] Status = RanToCompletion
[2025-02-15 20:23:10] IsFaulted = false
[2025-02-15 20:23:10] IsCanceled = false
[2025-02-15 20:23:10] IsCompleted = true
[2025-02-15 20:23:10] IsSucceeded = true
[2025-02-15 20:23:10] Result = null
[2025-02-15 20:23:10] ***
[2025-02-15 20:23:10] Id = 3
[2025-02-15 20:23:10] Status = RanToCompletion
[2025-02-15 20:23:10] IsFaulted = false
[2025-02-15 20:23:10] IsCanceled = false
[2025-02-15 20:23:10] IsCompleted = true
[2025-02-15 20:23:10] IsSucceeded = true
[2025-02-15 20:23:10] Result = null
[2025-02-15 20:23:10] [MAIN] КОНЕЦ
Ожидание завершения любого потока
Дочерние скрипты имеют такой же код как в предыдущем примере “Выстрелил и забыл”. Результат выполнения скрипта wait-for-any.djs
будет практически идентичен результату выполнения скрипта из предыдущего примера “Ожидание завершения всех потоков”.
****************************************
* Родительский скрипт wait-for-any.djs *
****************************************
DECLARE @task object
DECLARE @tasks array
PRINT '[MAIN] НАЧАЛО'
EXECUTE TASK 'file://task-1.djs' INTO @tasks
EXECUTE TASK 'file://task-2.djs' INTO @tasks
EXECUTE TASK 'file://task-3.djs' INTO @tasks
PRINT '[MAIN] WAIT ANY'
WHILE ARRAY_COUNT(@tasks) > 0
WAIT ANY @tasks INTO @task
PRINT '***'
PRINT 'Id = ' + @task.Id
PRINT 'Status = ' + @task.Status
PRINT 'IsFaulted = ' + @task.IsFaulted
PRINT 'IsCanceled = ' + @task.IsCanceled
PRINT 'IsCompleted = ' + @task.IsCompleted
PRINT 'IsSucceeded = ' + @task.IsSucceeded
PRINT 'Result = ' + @task.Result
END
PRINT '[MAIN] КОНЕЦ'
Возврат значения из фонового задания
Обращаем ваше внимание на то, что дочерний скрипт task-2.djs
завершается с ошибкой и возвращает вызывающему потоку исключение.
******************************
* Дочерний скрипт task-1.djs *
******************************
PRINT '[TASK 1] НАЧАЛО'
SLEEP 3 -- Имитируем работу
PRINT '[TASK 1] КОНЕЦ'
-- Возвращаем результат
-- вызывающему потоку
RETURN '[TASK 1] RETURN'
******************************
* Дочерний скрипт task-2.djs *
******************************
PRINT '[TASK 2] НАЧАЛО'
SLEEP 3 -- Имитируем работу
PRINT '[TASK 2] КОНЕЦ'
-- Возвращаем ошибку
-- вызывающему потоку
THROW '[TASK 2] ERROR'
******************************
* Дочерний скрипт task-3.djs *
******************************
PRINT '[TASK 3] НАЧАЛО'
SLEEP 3 -- Имитируем работу
PRINT '[TASK 3] КОНЕЦ'
-- Возвращаем результат
-- вызывающему потоку
RETURN '[TASK 3] RETURN'
*************************************************
* Родительский скрипт return-value-or-error.djs *
*************************************************
DECLARE @task object
DECLARE @tasks array
PRINT '[MAIN] НАЧАЛО'
EXECUTE TASK 'file://code/task/task-1.djs' INTO @tasks
EXECUTE TASK 'file://code/task/task-2.djs' INTO @tasks
EXECUTE TASK 'file://code/task/task-3.djs' INTO @tasks
PRINT '[MAIN] WAIT ALL'
WAIT ALL @tasks
FOR @task IN @tasks
PRINT '***'
PRINT 'Id = ' + @task.Id
PRINT 'Status = ' + @task.Status
PRINT 'IsFaulted = ' + @task.IsFaulted
PRINT 'IsCanceled = ' + @task.IsCanceled
PRINT 'IsCompleted = ' + @task.IsCompleted
PRINT 'IsSucceeded = ' + @task.IsSucceeded
PRINT 'Result = ' + @task.Result
END
PRINT '[MAIN] КОНЕЦ'
Результат выполнения скрипта return-value-or-error.djs
[2025-02-15 20:29:05] [MAIN] НАЧАЛО
[2025-02-15 20:29:05] [TASK 1] НАЧАЛО
[2025-02-15 20:29:05] [TASK 2] НАЧАЛО
[2025-02-15 20:29:05] [MAIN] WAIT ALL
[2025-02-15 20:29:05] [TASK 3] НАЧАЛО
[2025-02-15 20:29:08] [TASK 2] КОНЕЦ
[2025-02-15 20:29:08] [TASK 1] КОНЕЦ
[2025-02-15 20:29:08] [TASK 3] КОНЕЦ
[2025-02-15 20:29:08] ***
[2025-02-15 20:29:08] Id = 10
[2025-02-15 20:29:08] Status = RanToCompletion
[2025-02-15 20:29:08] IsFaulted = false
[2025-02-15 20:29:08] IsCanceled = false
[2025-02-15 20:29:08] IsCompleted = true
[2025-02-15 20:29:08] IsSucceeded = true
[2025-02-15 20:29:08] Result = [TASK 1] RETURN
[2025-02-15 20:29:08] ***
[2025-02-15 20:29:08] Id = 11
[2025-02-15 20:29:08] Status = Faulted
[2025-02-15 20:29:08] IsFaulted = true
[2025-02-15 20:29:08] IsCanceled = false
[2025-02-15 20:29:08] IsCompleted = true
[2025-02-15 20:29:08] IsSucceeded = false
[2025-02-15 20:29:08] Result = One or more errors occurred. ([TASK 2] ERROR)
[2025-02-15 20:29:08] ***
[2025-02-15 20:29:08] Id = 12
[2025-02-15 20:29:08] Status = RanToCompletion
[2025-02-15 20:29:08] IsFaulted = false
[2025-02-15 20:29:08] IsCanceled = false
[2025-02-15 20:29:08] IsCompleted = true
[2025-02-15 20:29:08] IsSucceeded = true
[2025-02-15 20:29:08] Result = [TASK 3] RETURN
[2025-02-15 20:29:08] [MAIN] КОНЕЦ