LINUX.ORG.RU

fileevent как работает?

 


0

3

Делаю клиент-серверное приложение, клиент на tcl. Хочу, чтобы fileevent клал сообщения в очередь (список). Затем как-то (по таймеру, через vwait, из самого fileevent) запускается процедура «обработать», которая берёт сообщение из очереди и обрабатывает его.

В очереди бывают сообщения с разным приоритетом, поэтому процедура «обработать» не совсем тривиальна: она может пошарить по очереди и потом взять какое-то событие.

В обычной жизни я завожу мьютекс, который блокируется при записи в очередь и на всё время, пока процедура «обработать» работает с очередью. При этом в одном треде делается чтение из потока и запись в очередь, а в другом треде - извлечение события из очереди и обработку. А в tcl что делать? При каких условиях можно гарантировать, что fileevent не вызовется во время выполнения процедуры «обработать» и не испортит процесс анализа очереди?

Я как-то не понял этот момент из чтения мануала.

Спасибо!

★★★★★

В Tcl на один интерпретатор один поток. fileevent гарантированно никогда не вызовется во время выполнения другого твоего кода.

Предположим, у тебя есть функции receive-impl и process-impl. Делай так:

fileevent $socket readable receive

proc receive {sock} {
    queue-push [receive-impl $sock]
    after idle process
}

proc process {} {
    process-impl [queue-pop]
}

vwait forever

На каждый after idle process process вызовется ровно один раз. Т.е. получили сообщение, положили в очередь, и запомнили как-нибудь потом обработать как руки дойдут.

Gentooshnik ★★★★ ()
Ответ на: комментарий от Gentooshnik

Спасибо! Я примерно так и делаю. Но вопрос в том, не может ли какое-то «происшествие» в системе влезть между строчек моего кода.

Например, я понял, что если я напишу

global a
set a 1
update
puts $a

то совершенно необязательно напечатается 1. Поскольку update может вызывать сторонние обработчики, к-рые поменяют a. А вот если я напишу просто

global a
set a 1
puts $a
Возможно ли, что напечатается не 1? Поток-то один, но есть ещё сигналы, которые вообще говоря (не знаю, как в tcl) могут прервать течение потока. Есть ещё vwait a. Вот меня волнует, а не может ли таймер или процедура, которая стояла на vwait a, выполниться после set a 1 и до puts $a ?

Или vwait и таймер сами ничего не делают, а только кидают сообщение в очередь?

den73 ★★★★★ ()
Ответ на: комментарий от den73

Все эти «прерывания» работают только в случае если у тебя программа в режиме «event driven».

anonymous ()
Ответ на: комментарий от den73

Кое-что влезть может, но не с помощью event loop. Я могу быстро сообразить два способа напечатать не 2 в последовательности

set a 1
puts $a

1) Переопределить процедуру set.

2) Установить отслеживание переменной a:

proc tr {n1 n2 op} {
    set ::$n1 2
}
trace add variable a write tr
перед предыдущим кодом приведет к тому, что будет напечатано число 2.

Но оба эти случая никак не связаны с event loop, так что смело можно считать, что между строк процедуры ничего другого не выполняется. update не в счет, так как он явно говорит «выполни все, что накопилось в event loop».

TeopeTuK ★★★★ ()
Ответ на: комментарий от den73

А вот vwait как раз и запускает этот самый event loop. Event loop всегда запускается в wish (графический шелл) и вот таким образом, через vwait, в tclsh. Файловые события требуют, чтобы event loop был запущен, поэтому избавиться от vwait не получится. Кстати, если цикл уже запущен каким-нибудь другим vwait или по умолчанию в wish, то внешний цикл при этом не обрабатывается (все новые события обрабатываются внутренним), а те события, которые накопились к вызову внутреннего vwait, лежат до его завершения. Так что лучше бы не делать вложенных циклов обработки событий. Идиома vwait forever означает обычно, что выход из цикла не предусматривается, хотя формально это так же как обычно «ждать, пока что-нибудь не запичет чего-нибудь в переменную forever».

TeopeTuK ★★★★ ()
Ответ на: комментарий от TeopeTuK

Ага, теперь картинка проясняется. У меня-то стоит как раз vwait a для конкретной переменной a, в которую запись происходит. Вот ещё страничка про vwait, где всё подробно расписано. http://wiki.tcl.tk/1302 . Впрочем, и в руководстве то же самое.

Я попробую написать своё понимание, а вы поправьте, пожалуйста, если что-то не так:

  1. Выполнение процедуры outer дошло до vwait a
  2. vwait a запустил цикл событий, outer осталась на стеке
  3. в цикле событий запустилась процедура WriteA, которая записала в a
  4. Завершается процедура WriteA и обратно попадаем во внутренний цикл сообщений
  5. Не обрабатывая следующие сообщения, а завершается vwait a. Следующие сообщения остаются в очереди и будут выполнены каким-то другим циклом обработки, если мы его запустим или вернёмся в него из outer.
  6. Продолжается выполнение outer

Верно? Таким образом, я могу не опасаться, что vwait нарушит поведение моего кода

set a 1
puts $a

Другое дело, что мой код основан на tkcon и в нём переопределена puts. Внутри неё вызывается update idletasks. Но эта угроза хотя бы понятна, а значит её можно контролировать. И вызов происходит в конце. Как минимум тут получается, что я не могу использовать puts для отладочных сообщений. Но это уже другая история...

den73 ★★★★★ ()
Ответ на: комментарий от den73

Если я правильно понимаю, то все так, кроме пункта 5, который чуть неточный. События в event loop выполняются пачками (подробности я не знаю, но точно не по одному), поэтому если в одной и той же пачке с WriteA окажутся еще события, то их обработчики тоже сработают. Остальные события и правда останутся для внешнего цикла или вовсе пропадут. Но не думаю, что на практике с такими тонкостями возникнут проблемы.

Ну а про tkcon и его puts ты и сам все понимаешь. В конце концов можно переопределить puts самому, если понадобится.

TeopeTuK ★★★★ ()
Ответ на: комментарий от TeopeTuK

Ладно, буду иметь в виду про пачки. Спасибо за разъяснения! На всяк случай - вот мой проект - IDE для Common Lisp, клиент на tcl/tk, сервер SWANK.

https://bitbucket.org/budden/clcon

den73 ★★★★★ ()
Ответ на: комментарий от den73

Возможно ли, что напечатается не 1?

Нет.

Поток-то один, но есть ещё сигналы, которые вообще говоря (не знаю, как в tcl) могут прервать течение потока.

Нету.

Есть ещё vwait a. Вот меня волнует, а не может ли таймер или процедура, которая стояла на vwait a, выполниться после set a 1 и до puts $a?

vwait - это бесконечный цикл update'ов.

Или vwait и таймер сами ничего не делают, а только кидают сообщение в очередь?

Gentooshnik ★★★★ ()
Ответ на: комментарий от Gentooshnik

Или vwait и таймер сами ничего не делают, а только кидают сообщение в очередь?

Не уверен, что все прояснилось, поэтому уточню. :)

«Сообщение» - это скрипт. Кусок кода, который выполняется в глобальном контексте.

after кидает одно сообщение в очередь обработки, возможно с задержкой.

filevent делает так, что каждый раз когда сокет становится readable/writable кидает сообщение в очередь обработки.

update обрабатывает все сообщения в очереди.

vwait обрабатывает все сообщения в очереди, но потом не возвращает, а ждет пока придут ещё сообщения.

Обычно программа пишется путём установки кучи штук, которые будут накидывать сообщения (after, fileevent, кнопки Tk и т.д.), потом в конце один раз вызывается vwait. Если посреди своего кода ОЧЕНЬ нужно обработать другие сообщения - вызывается update, но это крайне не рекомендуется именно из-за неочевидного результата даже в таком коде:

set a 1
update
puts $a
Gentooshnik ★★★★ ()
Ответ на: комментарий от den73

Понял общую идею.

У меня обычно работа с сервером асинхронная. fileevent читает сообщение из потока от сервера, кладёт его в мою переменную-очередь и кидает виртуальное событие «посмотри в очередь».

Но иногда нужно обработать следующую логику:

отправить сообщение на сервер
ждать ответа конкретного вида, ничего другого не делая
если придут другие сообщения, складывать их в очередь
когда придёт нужный ответ, обработать его
return

Во время выполнения этой допустимо, чтобы всё моё tcl-ное приложение «зависало».

Я для этого использую в процедуре, в цикле внутренний vwait моя очередь. Процедура написана так, что сообщения от моего сервера обрабатываются корректно. Но уже вижу, что могут проскочить любые другие события, вплоть до закрытия моего окна крестиком.

Видимо, надо будет выпилить моё vwait из обработчика?

Остаётся тогда только цикл из sleep и попыток чтения, мне такое не нравится, потому что если поставить большой sleep, то лаги, а если маленький - то процессор тратится.

Либо временно переключать поток в блокирующий режим чтения, но я не уверен, что это будет корректно работать.

В общем, вчера мне казалось, что всё уже понятно, а сегодня уже ничего не понятно. Т.е. как работает vwait и вообще события, я понял, а вот как написать свою программу - уже не очень.

Например, puts вызывает update idletasks, а внутри своего синхронного обработчика я вызываю puts. Так что для реально синхронного режима нужно выпиливать и puts - накапливать их и выполнять уже после завершения синхронного режима.

К тому же тезис о том, что tcl должен полностью перестать отвечать на события ГУЯ на время выполнения синхронного запроса, тоже спорный. Например, тогда я не смогу прервать синхронный запрос.

Тут как-то лучше подходит понятие «модального диалога». Но как таковое диалоговое окно мне не нужно. Потому что простейший синхронный запрос - это нажатие Tab, чтобы получить Completion идентификатора.

Т.е., по хорошему нужен какой-то режим, когда я могу перевести очередь в режим с приоритетами. Когда большинство событий откладываются, а некоторые избранные (мой fileevent и нажатие на кнопку «прервать») обрабатываются. idle, как я понял, тут не подходит, он вроде занят гуем. Есть ещё hook и uevent, но с ними я пока не разбирался.

Получился поток сознания. Извините.

den73 ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.