<< Click to Display Table of Contents >> Navigation: Проект > Элементы дерева объектов > Палитра ФБ > Служебные > Скрипт > Руководство и примеры > Работа с архивом данных > Выполнение асинхронных запросов |
В прошлом разделе мы выполнили обработку архива - загрузили набор значений за определенный интервал времени, а затем по очереди прошли по его элементам. В нашем примере архив был небольшим, и выборка тоже была короткая, но что будет если мы попробуем загрузить архив за несколько месяцев, и не по одной переменным, а например по 10? Естественно такой запрос может оказаться достаточно длительный. А что будет, если будет выполнятся обращение к архиву находящегося на удаленной базе данных, с которой пропала связь? Запрос будет еще дольше - и завершится только по таймауту соединения. При этом, пока выполняется запрос к архиву, весь поток выполнения будет остановлен. Данная проблема касается не только работы с архивом, но и других длительных операций - SQL запросов, обращений к WEB или FTP серверам, открытие больших файлов и т.д.
Здесь нужно отступить от темы, и рассказать про потоки в MasterSCADA. В MasterSCADA есть объекты первого уровня (т.е те что находятся одним уровнем ниже корневого):
Каждый из таких объектов выполняется в отдельном потоке. Объекты вложенные в объекты первого уровня - выполняются в его потоке. Т.е. в примере на скриншоте будет 5 потоков. До версии 3.12 на уровне системы MasterSCADA была настройка Количество рабочих потоков, со значением 3 по умолчанию - то есть количество потоков ограничивалось тремя. В настоящий момент по умолчанию количество потоков не ограничивается - за это отвечает настройка Выполнение в отдельных потоках. Убедитесь что в вашем проекте данный флаг установлен.
Если в одном из вложенных объектов, возникает какая-то длительная операция, то весь поток данного объекта останавливается пока операция не будет завершена. Поэтому, если у вас есть скрипты или расчеты работающие с архивом, это может приводить к "тормозам" в проекте. Далее мы опишем рекомендации, как избежать этой ситуации.
Если, как, например, в прошлом примере, вы не делаете больших выборок - значения ограничены архивом за 1-2 дня, и делаются по запросу оператора, то, как правило, можно не беспокоится о данной проблеме - даже если после нажатия кнопки оператором, часть объектов не будут обновляться 1-2 секунды, то ничего страшного не произойдет.
Если все же необходимо, чтобы обработка архивов не мешала работе, то можно поступить двумя способами. Первый способ, очень простой - нужно положить Скрипт (и возможно какие-то элементы для его управления) в отдельный объект, и у этого объекта поставить флаг Собственный цикл.
Установка данного флага выводит скрипт в независимый поток, и теперь его работа не будет влиять на опрос остального объекта в который он добавлен. Если таких скриптов у вас не много - это самое простое решение.
Если же скриптов работающих с архивом у вас много, и вы не можете сгруппировать их в одном объекте, то в таком случае можно несколько изменить код скрипта - сделать его запрос асинхронным.
Суть асинхронного запроса в следующем. В определенный момент времени, когда нужно выполнить какую-то длительную операцию, она вызывается отдельной задачей, которая порождает новый поток. При этом код в скрипте останавливается, а основной поток, который вызвал асинхронную операцию, отпускается и выполняется дальше. Затем, когда запрос завершился, можно обработать его результаты и записать их куда вам требуется.
Рассмотрим в качестве примера скрипт, который будет долго выполняться - в цикле мы будем считать до тысячи, и в каждом шаге цикла будем делать Sleep на 1 миллисекунду и посмотрим что будет происходить с переменными проекта.
Сам код и структура скрипта очевидны и комментариях не нуждаются. Переменная Команда 1 - имитационная, с режимом имитации Шум. По ней мы будем отслеживать что наш поток "висит".
Запустим режим исполнения и подадим сигнал на вход Начать.
На время работы скрипта, состояние входа Начать остается Выкл (дерево еще не обновилось, потому что цикл не завершен), а Команда 1 не меняется. Спустя некоторое время значения появятся:
Попробуем избавится от этой проблемы. Сначала попробуем простой вариант решения - поставим флаг Собственный цикл.
Запустим режим исполнения и проверим - теперь Команда 1 будет меняться, пока вычисляется скрипт.
Теперь попробуем изменить код нашего скрипта. Сначала сбросим флаг Собственный цикл.
В секцию using необходимо добавить два пространства имен:
using System.Threading.Tasks;
using System.Threading;
Приведем код целиком:
bool? M = false;
public override async void Execute()
{
if (Начать == true && M == false && ИдетВычисление != true)
{
ИдетВычисление = true;
await Calculate(); //асинхронный вызов метода Calculate
ИдетВычисление = false;
}
M = Начать;
}
private Task Calculate()
{
return Task.Run(() =>
{
uint Val = 0;
for (uint i = 0; i < 1000; i++)
{
Val = Val + i;
System.Threading.Thread.Sleep(1);
}
HostFB.FireEvent(1, "Вычисления готовы");
Результат = Val;
});
}
Обратите внимание что метод Execute помечен ключевым словом async - это означает что данный метод может вызывать асинхронные запросы.
Метод Calculate имеет возвращаемый параметр - Task, т.е. задачу. Это означает что данный метод можно вызывать асинхронно. Далее следует код в котором создается задача, и происходит ее запуск методом Run. Внутри данного лямбда-выражения и находится наш имитационный код. В конце вычисленное значение записывается на выход скрипта Результат.
Метод Calculate вызывается с помощью оператора await. В момент вызова метод вызывается, запускается задача, код скрипта останавливается, но вызвавший метод Execute поток отпускается и продолжает свою работу. Вызову метода предшествует запись значения true на выход ИдетВычисление - это сигнал, что идет обработка. При этом, в условии проверяется что идет вычисление не равен true - чтобы не запустить новое вычисление пока не закончилось старое. После того как асинхронный метод закончил работу, ИдетВычисление сбрасывается в false.
Как можно выполнить передачу и получение значения из асинхронной операции? Для пример рассмотрим ту же самую задачу, но теперь сделаем, чтобы вычисленное значение возвращалось из скрипта и записывалось на выход в методе Execute, и в нем же формировались сообщения. В качестве входного аргумента мы будем передавать до какого значения вести счет.
Исправленный код ниже:
bool? M = false;
public override async void Execute()
{
if (Начать == true && M == false && ИдетВычисление != true)
{
ИдетВычисление = true;
var Res = await Calculate(1000); //асинхронный вызов метода Calculate с параметром 1000
HostFB.FireEvent(1, "Вычисления готовы");
Результат = Res;
ИдетВычисление = false;
}
M = Начать;
}
private Task<uint> Calculate(uint Count)
{
return Task<uint>.Run(() =>
{
uint Val = 0;
for (uint i = 0; i < Count; i++)
{
Val = Val + i;
System.Threading.Thread.Sleep(1);
}
return Val;
});
}
С точки зрения передачи значений в функцию никаких трудностей нет - просто передается входное значение, как и в случае простого вызова метода.
С возвратом все несколько сложнее. При объявлении метода необходимо указать не просто Task, а также тип возвращаемого значения в угловых скобках, в нашем случае это тип uint. Аналогично в теле метода, необходимо создать и вернуть Task с таким же типом данных, а затем, уже в лямбда-функции вернуть значение данного типа.
Затем уже в методе Execute, мы проверяем есть ли значение в переменной Val, и в зависимости от этого выдаем нужное сообщение, а результат пишем на выход.
Если требуется передать не одного значение, а несколько, то можно создать структуру, в теле класса, и сформировать ее в асинхронном методе и вернуть вызывающему коду.
Какой из вариантов записи значений на выходы использовать - писать в асинхронном методе или же вернуть значение и записать в Execute? В первом случае есть риск того, что запись на выходы попадет между циклами опроса - например, вы записываете значение в 10 выходов, выполнили необходимую обработку в асинхронном запросе и в нем же пишите значения на теги, 5 тегов вы записали и они записались в одном цикле опроса объекта, а оставшиеся 5 - уже в следующем. Если данные выходы используются для вывода на мнемосхему (чаще всего так и будет) - это не страшно, но если же эти значения потом используются в каком-то алгоритме, то тогда следует вернуть эти значения, и записать в методе Execute - тогда они запишутся одновременно.
Готовый пример с двумя вариантами скрипта можно скачать по ссылке.
Примечание. Ключевые слова async и await доступны начиная с версии 3.13. Если у вас более старая версия, то данный код не скомпилируется. Вам нужно или обновиться до текущей версии, или создать асинхронную операцию с помощью метода Task.Start.
Теперь попробуем адаптировать наш пример с прошлого шага под асинхронный запрос:
public partial class ФБ : ScriptBase
{
struct ValStruct
{
public double? Val;
public DateTime? Tim;
public ValStruct(double? ValIn, DateTime? TimIn)
{
Val = ValIn;
Tim = TimIn;
}
}
bool? M = false;
public override async void Execute()
{
if (Найти == true && M == false && Начало.HasValue && Конец.HasValue && Начало < Конец)
{
ValStruct St = await GetMax();
Значение = St.Val;
МеткаВремени = St.Tim;
}
M = Найти;
}
private Task<ValStruct> GetMax()
{
return Task<ValStruct>.Run(() =>
{
var elem = HostFB.InputGroup.GetPin("Вход").TreePinHlp;
var k = elem.DataArchiveItem;
DateTime EndTime = Конец.Value.ToUniversalTime();
DateTime StartTime = Начало.Value.ToUniversalTime();
var mas = k.Read(StartTime, EndTime, false);
double? Val = null;
DateTime? TimeStamp = null;
foreach (var element in mas)
{
if (Val.HasValue == false || Convert.ToDouble(element.Value) > Val.Value)
{
Val = Convert.ToDouble(element.Value);
TimeStamp = element.Time.ToLocalTime();
}
}
return new ValStruct(Val, TimeStamp);
});
}
}
По сути весь код перенесен в асинхронный метод, из которого возвращается структура из значения double и DateTime, которая потом записывается на выходы.
Пример можно скачать по ссылке.
Есть еще один вариант обработки архивов - в нем, формируется специальный запрос в систему архивации, он попадает в общую очередь запросов и возвращает результат. Данный вариант не порождает никаких дополнительных потоков, и работает в рамках общей системы архивации - его следует использовать, при большом объеме скриптовой автоматизированной обработки архивной информации. Пример такого скрипта можно скачать по ссылке, основной код скрипта снабжен комментариями для лучшего понимания.