Начал я потихоньку оформлять свой первый "боевой" скрипт - "уж совсем была бы наша победа, но тут припёрся очередной Мальчиш-Кибальчиш": после остановки скрипта теряется управление. Локализация места ошибки привела вот к такому коду:
Код
function main()
f=true;-- признак работы скрипта (false - завершение)
c=0; -- малый счётчик прерываний
C=0; -- большой счётчик прерываний
... -- идёт начальная инициализация данных (вместо onInit)
while f do -- бесконечный цикл до остановки скрипта
sleep(1500); -- раз в 1.5 секунды запускаем утилиту опроса
small(); -- текущих данных и вывода их юзеру
end; -- конец бесконечного цикла
... -- идёт запись результатов в файл
message("Скрипт остановлен!");
end;
function small()-- обработчик прерывания по таймеру
local i=0; -- индекс текущего тикера
local j=0; -- значение последней цены
c=c+1; -- счётчик "мелких" прерываний
if c==10 then c=0; big();end; -- прошло 15 секунд, включаем анализ
while i<N do -- цикл по тикерам (опрос текущих курсов)
if a[i][9]~= 0 then -- если тикер присутствует в таблице
j=getParamEx(a[i][1][0],a[i][0],"LAST").param_value;
if j~=a[i][2] then -- если курс изменился
a[i][2]=j; -- запоминаем новое значение курса
SetCell(T,a[i][9],3,tostring(d0(a[i][2])));
end; -- конец условия "курс изменился"
end; -- конец условия "тикер присутствует в таблице"
i=i+1;-- переходим к следующему тикеру
end; -- конец цикла по тикерам
end
function big() -- более серьёзный обработчик
... -- идёт некий анализ данных
end
function d0(s) -- обрезка концевых нулей после запятой
s=tonumber(s); -- для числовых переменных
if s==math.floor(s) then s=math.floor(s) end
return s; -- возвращаем огрызок
end
function OnStop()-- вызов по нажатию кнопки "Остановка скрипта"
f=false; -- команда на прекращение бесконечного цикла
end
Если закомментировать вызов SetCell (а он прекрасно работает, таблица значениями заполняется), то по остановке и файл результатов записывается, и сообщение "Скрипт остановлен!" выскакивает. А "на нет и суда нет" - ни файла, ни месседжа! Насколько я понимаю, какая-то скотина, связанная с SetCell, не возвращает управление в main. Что делать? Ну не писать же в onStop окончание main!
function OnStop() ----------- Завершает цикл в функции main() ----------- IsRun = false; message(" Завершение работы моего интерфейса ===== ",1) return 10000 -- !! По истечении этого интервала времени (в млсек.), данного скрипту на завершение работы, функция main() завершается принудительно. -- При этом возможна потеря системных ресурсов. ! Если функция не выдает значение, то по умолчанию оно 5000 (5 сек.) end;
TGB, Нер, это мы уже проходили - раньше обработчик вызывался раз в 15 секунд, и там стояло "return 20000", сейчас это уже не требуется - 5 секунд на завершение предостаточно, но если поставить, не помогает: скрипт всё равно завершается принудительно.
Приколы на этом не кончились: добавил я после SetCell вывод ещё двух столбцов таблицы: j=a[i][5][a[i][1][4]-1]; SetCell(T,a[i][9],8,tostring(d0(j))); SetCell(T,a[i][9],9,string.format("%1.2f",a[i][2]/j*100-100));
И... управление чудесным образом вернулось! Закомментировал взад - а теперь всё работает! Кошмар какой-то!
Довёл код индикации практически до финального варианта - всё прекрасно работало. Запустил через час - опять теряет управление. ВААПЩе НИЧЕГО НЕ ДЕЛАЛ СО СКРИПТОМ!
Владимир написал: Запустил через час - опять теряет управление. ВААПЩе НИЧЕГО НЕ ДЕЛАЛ СО СКРИПТОМ!
Какуя версию QUIK Вы используете? Если >= 8.5, то можете посмотреть мой комментарий https://forum.quik.ru/messages/forum10/message48194/topic5527/#message48194 . Этот комментарий относится и к QUIK 8.9, который "падает" на моей тестовой программе автоматической памяти QLua (отосланной поддержке QUIK 15.08.20) в разные моменты, но в интервале пяти минут. В версиях QUIK < 8.5 эта тестовая программа без проблем работает неделями.
Владимир написал: Насколько я понимаю, какая-то скотина, связанная с SetCell, не возвращает управление в main.
Верно. Как раз тот случай, когда для понимания причины надо поковыряться в кишках квика. Значит, что мы имеем. Все колбеки выполняются в главном потоке квика, в нем же живут все окна и очередь сообщений. Мейн выполняется в своем отдельном потоке. Вы жмете кнопку остановить, сообщение обрабатывает главный поток квика, и обрабатывает следующим образом: 1) вызывает колбек OnStop 2) ждет 5 секунд или сколько возвращено из OnStop, причем ждет тупым WaitForSingleObject(hMainThread, timeout). По выходе из этой функции квик проверяет, почему вышли. Если завершился поток мейна, все ок, если по таймауту - поток мейна прибивается принудительно через TerminateThread. 3) в любом случае зачищаются остатки скрипта и завершается обработка сообщения от кнопки остановить.
Теперь смотрим, что будет происходить в приведенном случае. Вы жмете кнопку остановить, главный поток квика посылает OnStop, скрипт оттуда возвращается, главный поток квика входит в WaitForSingleObject. В этот момент мейн вызывает SetCell, что под ковром приводит к отправке сообщения окну скрипта (живущему в главном потоке). Но главный поток сообщений не обрабатывает, он стоит на WaitForSingleObject. Ваш мейн зависает в ожидании ответа на сообщение. Через 5 секунд WaitForSingleObject возвращается по таймауту, квик видит таймаут и прибивает мейн принудительно. Собственно и все.
Очевидно, просто поменять в мейне sleep() и small() местами уже даст некоторый эффект, но без гарантий. Лучше проверять флаг f перед каждым вызовом SetCell.
TGB, А я знаю, какую версию? Ща посмотрю... на одном Квике 8.8.4.3, на втором - 8.7.1.3. Блин, а отлаживался почти всё время на первой. О, сколько нам открытий чудных!
Anton, Ой! Не надо мне ковыряться в кишках квика! Я уж столько лет ковырялся в разных кишках, что...
Ах, сволочь какая! Стало быть... нет, секундочку! У меня же мейн НЕ вызывает SetCell - это происходит в обработчике, то бишь в главном потоке. Ладно, завтра попробую что-то сообразить на свежую голову, спасибо за инфу.
Вот меня больше всего и убивает нестабильность: тут играем, тут не играем, тут мы селёдку заворачивали.(с) А sleep() и small() я уже поменял местами (из других соображений - в мейне таблица только инициализируется, а наполняется данными уже в обработчике - чтобы не было паузы для юзера) - это тоже не помогает. И мне это ОЧЕНЬ не ндравицца! Значит, скрипт у меня проработает целый день, а во время записи результатов начнёт выдрипываться, и все результаты псу под хвост? Неужели в OnStop конец мейна присобачивать придётся? Меня от одной мысли об этом воротит - это уже не код, а урод получается.
Владимир написал: У меня же мейн НЕ вызывает SetCell - это происходит в обработчике, то бишь в главном потоке.
Все же small это не более чем функция, выполняется в том потоке, в котором вызвана, в данном случае в потоке мейна. Ее в принципе можно в мейн заинлайнить и ничего не изменится (кроме читаемости скрипта). Попробуйте огородить SetCell условием if f then SetCell(...) end. Хотя строгих гарантий от дедлока оно тоже не даст, просто уменьшит его вероятность; если кнопку стоп удачно нажмут между ифом и вызовом, таки повиснет.
Anton написал: Все же small это не более чем функция, выполняется в том потоке, в котором вызвана, в данном случае в потоке мейна.
Я до сего момента не использовал таблицы QUIK и потому проверил то, что описывает Anton. Похоже, он описывает то, что есть. То есть, некорректное завершение скриптов по событию OnStop. Действительно, поддержка (обслуживание) таблиц QUIK выполняется в том же (основном потоке), что и обработка колбеков (по моему мнению плохое решение). Поэтому (как написал Anton), если нажатие кнопки "Остановить" произошло в тот момент, когда не завершилась отработка (в основном потоке) SetCell(...) , то возникает ситуация, которую Вы наблюдаете. Это относится и к версиям QUIK < 8.5. Наверное, стоит "выкатить", обнаруженное Вами, поддержке QUIK. Фиксацию ваших результатов, имеет смысл, перенести в OnStop до строки: f=false
Anton написал: Вы жмете кнопку остановить, главный поток квика посылает OnStop, скрипт оттуда возвращается, главный поток квика входит в WaitForSingleObject. В этот момент мейн вызывает SetCell, что под ковром приводит к отправке сообщения окну скрипта (живущему в главном потоке). Но главный поток сообщений не обрабатывает, он стоит на WaitForSingleObject. Ваш мейн зависает в ожидании ответа на сообщение. Через 5 секунд WaitForSingleObject возвращается по таймауту, квик видит таймаут и прибивает мейн принудительно.
Цитата
Anton написал: проверять флаг f перед каждым вызовом SetCell.
На мой взгляд, это уже зона ответственности квика: при вызове SetCell проверить не было ли команды OnStop. И если была, то игнорировать все оконные команды (раз они всё равно не могут быть исполнены) и переходить к выполнению следующих инструкций.
Цитата
TGB написал: Наверное, стоит "выкатить", обнаруженное Вами, поддержке QUIK.
Anton, Ах, да, конечно - это ведь только по смыслу обработчик прерываний, а по факту всего лишь поганый CALL.. .
Вот же гнида! Обложил все действия с таблицами "if f then" - ФАЙЛ появился, ТАБЛИЦА обнулилась (я думал, DestroyTable должна бы её вообще уничтожить), а месседж финальный - НЕ появился!
"Он то плакал, то смеялся, то щетинился как ёж — он, гад, над нами издевался. Ну сумасшедший — что возьмёшь?"(С) В конце концов, хрен бы с ним, с месседжем - главное, файл, но Я НЕ ПОНИМАЮ, как она работает, от слова "совсем"!
TGB, Ну, если эта скотина хоть раз мне загубит файл результатов, придётся перенести это дело в OnStop, хотя и очень не хочется.
Старатель, О! На мой взгляд ТОЖе "это уже зона ответственности квика"!
А, ну да - ведь DestroyTable у меня гарантированно вызывается при f=false, так что, видимо, теперь он и вешает всё это дело, а файл успел записаться до того.
TGB написал: Какуя версию QUIK Вы используете? Если >= 8.5, то можете посмотреть мой комментари
Странно, конечно, не все ситуации в QUIK возникают из-за сбоев управления автоматической памятью QLua 5.3.5, однако, меня поражает, что многие, похоже, не понимают, что, если сбоит память QLua 5.3.3, то можно ожидать всего что угодно, в любых своих программах.
Да, поэкспериментировал сегодня немного на работающем рынке (убил DestroyTable - появился финальный message). Будем считать, что всё работает. Спасибо всем, особенно Anton,
Хотелось бы обсудить ещё одну проблему - на этот раз "с весёлыми картинками и большими буквами".
Таблица от работы скрипта для юзера оказалась чрезвычайно информативной - настолько, что мне захотелось пользоваться только ею, если я сам хочу торговать, а не [только] доверять делать заявки роботу. Но тут выявились очередные нюансы:
1. Не работает раскраска ячеек: вызов SetCell меняет содержимое ячеек "автоматически", а вот на SetColor она пилюёт: всё остаётся чёрно-белым, а я бы хотел раскрасить таблицу как попугая: цвет - это тоже информация, и весьма наглядная.
2. Попытка динамически обновлять количество строк в таблице закончилась крахом. Я делал так: а) После создания таблицы в main и прописывания в ней столбцов, я набиваю её пустыми строками, соответствующие тем тикерам, которые меня интересуют (на старте это все тикеры, по которым у меня в данный момент что-то куплено). iRow=InsertRow(iT,-1); И вообще не набиваю их мясом (только запоминаю айдишки строк в своей таблице тикеров). Появляется пустая таблица с номерами строк. б) В "малом" прерывании (1.5 секунды) я пробегаюсь по тем тикерам, для которых были созданы строки и набиваю их всякими данными (последняя цена и т.п.) с помощью SetCell - в таблице тут же отображаются эти данные, которые обновляются при следующих прерываниях, если курс изменился или были какие-то сделки. в) а вот в "большом" прерывании (15 секунд) я хочу изменять набор тикеров, показываемых юзеру - убирать те строки, которые ему в данный момент не интересны и добавлять те, которые стали представлять интерес. Делаю я это так: while i<N do -- цикл по тикерам k=0; -- признак нужности тикера в таблице (по умолчанию не нужен) ... -- тут кое-какой расчёт, который может установить k=1 (тикер нужен в таблице). Далее парочка "ифов":
if k==0 and a[i][9]~=-1 then -- тикер есть, а его быть не должно DeleteRow(iT,a[i][9]);a[i][9]=-1;end if k~=0 and a[i][9]==-1 then -- тикера нет, а он нужен a[i][9]=InsertRow(T,-1);end i=i+1; -- переходим к следующему тикеру end; -- конец цикла по тикерам a[i][9] - это, естественно, ID строки, полученный от InsertRow и хранящийся в моей таблице для i-го тикера
Мясом строки тоже не наполняются (как и раньше, это сделает "малое" прерывание"). Запускаю - работает, часть стартовых строк из таблицы пропали, но... не все и не те. Вставка тоже работает непонятно как: в разных строках появляются одни и те же тикеры (с разными значениями данных) - в общем, прорисовываются удалённые строки и не прорисовываются неудалённые. Какой-то закономерности в этом я пока не заметил. Вопрос: кто и когда перерисовывает таблицу в окне Квика, и можно ли заставить её делать это принудительно - в доках я такого почему-то не нашёл.
Владимир написал: 1. Не работает раскраска ячеек: вызов SetCell меняет содержимое ячеек "автоматически", а вот на SetColor она пилюёт: всё остаётся чёрно-белым, а я бы хотел раскрасить таблицу как попугая: цвет - это тоже информация, и весьма наглядная.
Для анализа нужен пример кода, на котором воспроизводится проблема.
Цитата
Владимир написал: 2. Попытка динамически обновлять количество строк в таблице закончилась крахом. Я делал так:
Для анализа нужен пример кода, на котором воспроизводится проблема. Словесное описание во всех подобных вопросах только вносит смуту и порождает лишние вопросы. Проще - быстрее - надежней, привести пример кода на котором повторяется проблема (а не пару строк), тогда и решение Вы получите в разы быстрее.
Sergey Gorokhov,Господи, да я и привёл пример кода, даже с комментариями!
SetCell (iT,1,2,"SomeValue); -- Значение в столбце 2 строки 1 обновляется SetColor (iT, 1, 2, 255, 0); -- цвет ячейки остаётся прежним
Код на удаление и вставку строк вообще "боевой"! Ну, добавлю я пропущенное:
Код
k=0; -- признак ненужности тикера в таблице
a[i][2]=getParamEx(a[i][1][0],a[i][0],"LAST").param_value;
j=a[i][2]/a[i][1][7]*100-100; -- текущее изменение средней цены
if j<-5 then k=1;end -- провалившиеся тикеры показываем
if a[i][1][5]>0 then -- если есть ставки по тикеру
j=a[i][2]/a[i][5][a[i][1][4]-1]*100-100;
if j>3 or j<-3 then k=1;end -- тикеры с отклонившейся последней ставкой
end; -- конец условия "есть ставки по тикеру"
if k==0 and a[i][9]~=-1 then -- тикер есть, а его быть не должно
...
Sergey Gorokhov, Я не понимаю, ЧТО Вы считаете "примером кода". Информация о проблеме изложена ПОЛНОСТьЮ! Как ранее(чуть выше) была ПОЛНОСТЬЮ изложена другая проблема, про потерю управления при передаче данных между потоками, и даже было открытым текстом заявлено, что это "зона ответственности Квика" и даже, что эта проблема была поднята более года назад - что толку-то? Пришлось решать проблему с помощью своих костылей.
Вы типы параметров этой функции в документации смотрели? 255 - это не цвет. Используйте либо функцию RGB, либо число hue цвета. Плюс надо не забывать передавать цвета выделеннной ячейки.
Nikolay, Я не только смотрел - я ПРОВЕРЯЛ! RGB возвращает число от 0 до 0xFFFFFF, где младший байт - это R, а старший - это B. Так что в моём примере ячейка должна бы окраситься в красный цвет, а цвет текста должен быть чёрным. А выделенная ячейка мне нафиг не нужна - таблица чисто информационная.
Ну вот Вам простой пример. Для одной ячейки функция вызывается со всеми аргументами, для другой нет
Код
local sec_code = 'SRZ0'
local class_code = 'SPBFUT'
local sleep = _G.sleep
local isRun = true
local t_id = nil
local SeaGreen = _G.RGB(193, 255, 193)
local RosyBrown = _G.RGB(255, 193, 193)
local getParamEx = _G.getParamEx
local GetCell = _G.GetCell
local SetCell = _G.SetCell
local SetColor = _G.SetColor
local ds
local def_c = _G.QTABLE_DEFAULT_COLOR
function _G.OnParam(class, sec)
if isRun and t_id and sec == sec_code and class == class_code then
local last_price = tonumber((getParamEx(class_code, sec_code, 'LAST') or {}).param_value) or 0
local last_vol = tonumber((getParamEx(class_code, sec_code, 'VALTODAY') or {}).param_value) or 0
local lp = GetCell(t_id, 1, 0).value or last_price
SetCell(t_id, 1, 1, tostring(last_vol), last_vol)
SetCell(t_id, 1, 0, tostring(last_price), last_price)
if lp < last_price then
SetColor(t_id, 1, 0, SeaGreen, def_c, SeaGreen, def_c)
SetColor(t_id, 1, 1, SeaGreen, def_c)
elseif lp > last_price then
SetColor(t_id, 1, 0, RosyBrown, def_c, RosyBrown, def_c)
SetColor(t_id, 1, 1, RosyBrown, def_c)
end
end
end
local function CreateTable()
t_id = _G.AllocTable()
_G.AddColumn(t_id, 0, "price", true, _G.QTABLE_DOUBLE_TYPE, 15)
_G.AddColumn(t_id, 1, "vol", true, _G.QTABLE_DOUBLE_TYPE, 15)
_G.CreateWindow(t_id)
_G.SetWindowPos(t_id, 90, 120, 270, 100)
_G.InsertRow(t_id, 1)
end
local function event_callback(_, msg)
if (msg == _G.QTABLE_CLOSE) then
isRun = false
end
end
function _G.OnInit()
CreateTable()
_G.SetTableNotificationCallback(t_id, event_callback)
end
function _G.main()
ds = _G.CreateDataSource(class_code, sec_code, 1)
isRun = ds ~= nil
if ds then
ds:SetEmptyCallback()
end
while isRun and ds do
sleep(500)
end
end
function _G.OnStop()
isRun = false
ds:Close()
ds = nil
if t_id and not _G.IsWindowClosed(t_id) then
_G.DestroyTable(t_id)
end
end
Nikolay, И что этот пример должен демонстрировать? Тупо запустил в Квике - выскочила табличка с одной пустой строкой и столбцами "price" и "val!", чёрно-белая с головы до пят.
Видимо уже ничего... Смените инструмент на тот, по которому у Вас есть поток данных, раз SRZ0 нет. Но Вам уже разработчики ответили, так что просто добавьте параметры при вызове.
Владимир написал: ОЙ! Спасибо, конечно, Sergey Gorokhov , И повторяю пожелание: нельзя ли дать возможность редактировать свои комменты - скажем, в течение 5 минут?
К сожалению ядро форума не предоставляет такой возможности. В ближайшее время подобный функционал к добавлению не планируется.
Кажется, я начинаю понимать, что происходит при удалении/вставке строк: я-то к ним отношусь, как к ключам, а утилиты работы с таблицами, похоже, принимают их за индексы. Неприятно...
Сделал для себя два вывода из вчерашних-сегодняшних экспериментов:
1. Вставку строк (InsertRow) следует производить всегда в конец таблицы (код -1) - тогда индексы и ключи совпадают, а вот DeleteRow не следует делать вообще, поскольку в этом случае у исполнителя крыша едет, и данные начинают попадать не в те строки. Не нашёл ничего лучшего, чем при необходимости удалить строку обнулять всю таблицу (Clear) и заново перенабить в ней все строки, которые должны отображаться.
2. При описании столбцов (AddColumn) не задавать им никаких QTABLE_INT_TYPE, QTABLE_DOUBLE_TYPE - оставить только QTABLE_STRING_TYPE, и при занесении значения в ячейки (SetCell) заворачивать значения в tostring - тогда, по крайней мере, сортировка по столбцам работает именно как сортировка строк, а не выдаёт результаты, от которых глаза на лоб лезут.
3. Ну и, конечно, как мне тут подсказали, любой чих, любые действия с таблицей должны заворачиваться в проверку флага, что кнопка останова скрипта не нажата.
Возьмите за правило, что при сложении строк надо приводить значения к типу строка. lua умеет динамически преобразовать выражение вид: "a"..5 Но не умеет такое: "a"..nil Зато сможет так: "a"..tostring(nil)
Если у Вас в SP[a[i][1][1]] ничего нет, т.е. nil, то и будет ошибка.
Sergey Gorokhov,Нго у меня-то воспроизводится! И что делать?
i - переменная цикла по тикерам a[i][1][1] - код валюты для данного тикера (в данном случае 2 - евро) SP - сумма средств, вложенных в акции по данной валюте (считается в цикле).
Nikolay написал: Если у Вас в SP[a[i][1][1]] ничего нет, т.е. nil, то и будет ошибка.
он говорит что в SP[a[i][1][1]] что-то есть. иначе, вот это ..SP[2]); - вернуло бы nil, а оно возвращает "0: SP[2]=0" a[i][1][1]] равно 2, потому что message(i..": SP["..a[i][1][1].."]"); -- печатает "0: SP[2]"
Владимир, Логично предположить что Вам надо привести свою версию кода которая не работает. ПОЛНУЮ версию кода, а не пару строк которые ничего не значат
Sergey Gorokhov, Не логично. Там алгоритмически сложный код, в котором любой запутается, а тут простейшее действие, выделенное мною после "ругательств" интерпретатора на арифметику.
Я вам говорю не про то, что надо тип индекса менять, приведя его к числу. А про то, что лучше складывать строки со строками, хоть lua и динамический язык. А то что индекс 2 и индекс "2" - это разные вещи, это уже вопрос дизайна. Хотите использовать нечитаемые конструкции типа a[b[1][c[2]]] - пожалуйста.
Владимир написал: Там алгоритмически сложный код, в котором любой запутается, а тут простейшее действие, выделенное мною после "ругательств" интерпретатора на арифметику.
1) Поверьте, поддержку это совсем не пугает 2) Вы понимаете что без отслеживания значений в "a" и "SP" разобраться в причинах не представляется возможным? 3) Можно повторить пробелму на отдельном, более компактном коде. 4) Если не хотите сотрудничать то увы ничем помочь не можем.