Консультация № 168930
04.06.2009, 11:56
0.00 руб.
0 16 0
Уважаемые эксперты. Вопрос по многопоточности.
Туплю. Нужна помощь. Ни для кого не секрет, что в дельфях есть специальный класс TThread (или TIDThread). Но если плодятся потоки (нити) для внутренних нужд, то работают потоки так сказать "псевдопараллельно". Иными словами, если какой-то поток наглухо завесить, то остальные нервно курят бамбук. Разумеется, можно внутри "тяжелого" потока ставить application.processmessages для его временного прерывания, чтобы отработали остальные потоки, в том числе и обработчик событий приложения, но это неэстетично и не всегда возможно (в том числе при установке такого прерывания общее время счета может слишком сильно увеличиться). Конечно, когда приложение "не отвечает" операционной системе - это не есть хорошо.
В-общем, есть задача на фоне тяжелого потока запустить работу других потоков, в том числе и диагностического из серии "я живой" чисто визуально для пользователя.
Нужен чистый параллелизм, кто-нить сталкивался с подобной задачей?

Обсуждение

давно
Студент
15716
139
04.06.2009, 12:38
общий
Потоки не зависят друг от друга, приложение зависнет тольког если главный поток зависнет. Если зависнет любой из TThread'ов c остальными ничего не случиться, но только если вы не ждете завершения зависшего потока. Так что ваша проблема скорее всего в другом.
Неизвестный
04.06.2009, 12:52
общий
Всё так. Почти. Остальные потоки будут ждать, когда операционная система передаст управление на них. А операционная система это сможет сделать только тогда, когда "зависший" поток (точнее, поток, в котором идет тяжелая математика) соизволит прерваться. При бесконечном цикле в одном потоке (я утрирую ситуацию) остальные потоки не отрабатывают, ждут своей очереди. Эту ситуацию можно обойти? Фактически создать дочерний процесс со всеми вытекающими последствиями.
Например:

type
tmythread = class(tthread)
procedure Execute; override;
end;

...
procedure tmythread.Execute;
var
stop: boolean;
begin
inherited;
stop := false;
while not terminated do
repeat until stop;
end;
Безо всяких synchronize (хммм или же с ним)

приведет к зависанию приложения и остановке всех бушевавших до этого процессов.
Неизвестный
04.06.2009, 12:57
общий
Уточнение... Александр Романов в принципе прав. Сие есть факт.
Архитектура программы такова, что "тяжесть" исполняется именно в главном потоке... Переписывать программу - овчинка выделки не стоит. Может есть какой обходной путь оживить дочерние потоки?
давно
Мастер-Эксперт
425
4118
04.06.2009, 13:00
общий
Foxbox:
Почему "переписывать"? Просто выделите тяжёлые вычисления в отдельную процедуру и запускайте её не в главном потоке, а создавайте дополнительный. Главный поток - только для кнопок и прерывания всех других потоков. Syncronize нужны только если Вы из вторичного потока обращаетесь, например, к главному, чтобы вывести результат в Label. Согласитесь, это случается крайне редко.
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
Неизвестный
04.06.2009, 13:18
общий
Хмммм... Запускать TADOQuery, которая связана с гридом в отдельном потоке (типа отчет формируется)... Не знаю, чем это закончится, но в любом случае хотелось бы получить реализацию на уровне самого компонента... Проще, наверное, будет использовать в этом случае асинхронный метод доступа к данным.
Но как быть с методами визуальных компонентов? Например, функция экспорта данных из связанного с гридом ДатаСета, например, в экселя.
Радует то, что и грид и кверя перекрыты собственными компонентами, так что есть куда веселиться...
давно
Мастер-Эксперт
425
4118
04.06.2009, 13:30
общий
Foxbox:
Ни работа с ADO, ни экспорт чего бы то ни было куда быто ни было не являются работой с визуальными компонентами. Вы ведь можете из метода любого визуального компонента вызвать процедуру (хотя при чём тут визуальный компонент... )? Вот и оформляйте её в отдельный поток - хоть конвертирование, хоть получение данных из таблицы базы данных. Визуально там ничего абсолютно не работает.
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
Неизвестный
04.06.2009, 13:43
общий
Всё так, от визуальности там и следа нет... обычные методы. Только если метод новый в объекте... В принципе с новыми методами нет ничего сложного. Делаем свой класс tthread с прописанным новым методом. В конструкторе основного объекта создаем объект нашего модифицированного класса потока, запускаем поток, радуемся жизни. А если метод перекрывается... Как вынести метод tADOQuery.opencursor в другой процесс? Уж очень хочется воспользоваться зарезервированным словом inherited. Или не пользоваться? А вызвать его внутри потока-свойства объекта tADOQuery, а в перекрытом OpenCursor вызвать mythreadobject.Resume? Взорвется где-нить? Что-то не хочется устраивать эксперименты
давно
Мастер-Эксперт
425
4118
04.06.2009, 14:09
общий
OpenCursor - защищённый метод, т.е. конечному пользователю его вызывать не нужно и даже вредно.
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
Неизвестный
04.06.2009, 14:33
общий
"OpenCursor - защищённый метод", спору нет. Конечный пользователь спокойно вызывает open. А в перекрытом OpenCursor производится перекомпиляция макросов на понятные структуры TSQL (ну, фактически подмена содержимого SQL.Text) + трассировка текста запроса в лог программы + ошибки, если те возникнут после вызова inherited OpenCursor, + установки дополнительных коннектов, если нужно исполнить запрос не в основном коннекте к серверу. В-общем, многое чего происходит со страдальцем-ADOQuery. В результате отладка и поиск ошибок очень сильно упрощены. Посмотрел на лог, сразу видишь текст запроса, время его исполнения, ошибку, если таковая появилась... А писать тексты запросов вообще одно удовольствие. Хоть полскриптов загоняй в макросы (макросы могут лежать отдельным файликом, поставляемым вместе с exe), а потом при компиляции они развернутся в текущую их реализацию: как результат уменьшение кода, легко решаемая задача совместимости с серверами на разных платформах и т.д. Типа "всё круто", вот только теперь повесить юзверю какую-нить мессагу типа "жди, процесс бушует". Ясное дело, что большинство запросов будет работать от силы 1-2 секунды, нет смысл выводить инфу об ожидании... А если прога щупает, что запросец ушел в раздумья секунд на 3, то можно и повесить...
Неизвестный
04.06.2009, 14:48
общий
Foxbox:
Во первых
Нужен чистый параллелизм, кто-нить сталкивался с подобной задачей?
нет никакой параллельной работы.. Если у Вас реально не два и более процессоров, если приложение специально не написано с учетом нескольких процессоров.
ОС Windows раздает всем ресурсы процессора по принципу карусели - т.е. в каждый момент времени может работать только одна задача. И этот код писался еще в эпоху когда народ и не слышал о многопроцессорности. таким образом, разделение на потоки есть ничто иное, как "обособление" вычислительных процессов.

Главный поток (main) порождает другие, и следит за очередью сообщений (в том числе и для обработки событий визуальных компонентов).
Задача программиста в том, что бы долгий или "тяжелый" процесс перекинуть в отдельный поток, периодически из него или посылая сообщения или напрямую (в случае с Delphi - синхронизация) через визуальные компоненты (например, ползунок) о ходе выполнения операции.

Следовательно, в Вашем случае, надо либо действительно пересмотреть логику (кстати, хороший тон - всю работу с БД как и компоненты, размещать в отдельном модуле - Data Module. А из него уже вызывать удобные функции, методы, свойства и т.д.)
Или, использовать сообщения для общения с главной формой (или той, с которой Вам надо) в этом случае, ресурсы не занимаются, и обновление произойдет согласно очереди.
Неизвестный
04.06.2009, 15:20
общий
(
кстати, хороший тон - всю работу с БД как и компоненты, размещать в отдельном модуле - Data Module. А из него уже вызывать удобные функции, методы, свойства и т.д.

offtop.
гы-гы, держите меня семеро... Без обид.
Если делается тяжелый проект с большой функциональностью, то сваливать все квери в один DataModule по меньшей мере безумие. Сотни форм десятков справочников и в одном DataModule - прочитать код нереально. Особенно если весь проект нарезан на bpl-ки по функциональному признаку: платежи отдельно, догворы отдельно, планирование, справочники тоже отдельно, клиенты сами по себе живут... А тут всё в кучу. Сие есть ущербный подход. Если есть форма со своими уникальными dataset, то эти dataset и живут на форме. Другое дело, что функцию чтения, например, текущего курса валют есть смысл вынести в так называемый модуль общих функций...

Ну а сообщения... если "тяжесть" запустилась в основном потоке, то бесполезно слать сообщения дочерним потокам. Хотя... SendMessage скорее всего даст возможность отработать дочерним процессам, но это легко решается более простым способом: Application.ProcessMessages.
А вот при синхронном запуске квери TADOQuery.OpenCursor прога уходит "в себя" без права на реабилитацию до тех пор, пока эта долбанутая борландовская реализация АДО не закончит работу. Так что как вариант выносить вызов OpenCursor в дочерний поток, блокировать Owner, чтобы юзвери не шебуршали своими мышками по контролам до окончания выполнения запроса, или колдовать с асинхронным выполнением запроса, но в этом случае без переделки основной проги, боюсь, не обойтись.
Неизвестный
04.06.2009, 15:52
общий
Foxbox:
Если делается тяжелый проект с большой функциональностью, то сваливать все квери в один DataModule по меньшей мере безумие.

Мне нет смысла с Вами спорить. Вы поставили для себя задачу решить в лоб. Вам же говорят, как это можно сделать правильно.
Большие задачи с множеством форм, и БД с более чем 200 таблиц и т.д. не редкость и не у вас одного.
Логика БД гораздо проще делается в Data Module которых может быть множество - это по сути обычные модули pascal которые просто имеют заранее подключенные библиотеки. К тому же, подключение каждого датасета увеличивает потребление памяти как минимум на 100 кб.. Потому, не делают в профессиональной среде множество датасетов а обходятся по максимуму SQL (Query) - т.е. минимум компонентов. Труд программиста конечно усложняется но качество повышается.

Тяжелые задачи, выносятся в отдельный поток, дабы не нагружать основной. Если есть запрос, который работает долго, и при этом дело не в БД (например, несколько миллионов записей из сотен таблиц.. со сложной логикой - т.е. банковские транзакции, например), то имеет смысл его вывести в отдельный поток. Он молча отрабатывает показывая индикатор (что жив еще и работа ведется) на форме его запустившей.

PS: не стоит так иронизировать, с вами тут не шутят, а пытаются подсказать и далеко не дилетанты.
Неизвестный
04.06.2009, 16:27
общий
На самом деле разумно бизнес-логику оставлять на сервере баз данных, используя процедуры и функции. Будет гораздо быстрее работать, чем пересылать "банковские транзакции" на клиентскую часть и, подтверждая их, возвращать данные на сервер. Собственно, у меня так и сделано. Клиентская часть используется только для отображения результатов расчета, ну и какие-то выборки ещё сидят. Да и с блокировками как-то безопаснее работать именно в одном батче сервера. А то не приведи господи exception появится... а rollback сделан не будет программистом. Результат предсказать нетрудно.

Ну, я понял Вашу мысль. Бизнес-логика живет отдельно, визуализация живет отдельно. Где именно (на клиенте или на SQL-сервере) реализована БЛ - неважно. Не стоит обижаться на мои комменты, у меня, чай, тоже не десяток строк кода за бортом. Но тем не менее вполне косячный момент из моей практики хочется решить малой кровью... В любом случае все возможные пути решения моей траблы эксперты изложили. Дело за малым - реализовать или на уровне компонентов (что технически грамотно), или на уровне приложения (в местах особо тяжелых запросов).
Благодарю всех, принявших участие в дискуссии.
Тем не менее, мои слова не означают окончание обсуждения. С удовольствием буду знакомиться с новыми мыслями и идеями.
давно
Мастер-Эксперт
425
4118
04.06.2009, 16:30
общий
Foxbox:
К сожалению потерял нить Ваших рассуждений, простите.
Логирование запросов с целью их оптимизации лучше проводить программисту, а не конечному пользователю. Это сильно замедляет процесс. Вдобавок пользователь всё равно не сможет ничего предпринять прочитав логи. Т.е. логирование дальше процесса разработки не идёт (в основном , конечно другое дело, если типы данных и их обработка постоянно меняются).
Реально долгий запрос может быть в двух случаях:
1. Вы получаете очень большой объём данных. При этом реальным тормозом будет сеть, а не программа или сервер.
2. Вы проводите очень сложную обработку полученных данных. Здесь уже Ваша программа к обработке не имеет никакого отношения, т.к. конечные цифры для представления пользователю получает сервер и время обработки зависит от его мощности.
Многопоточность, как ни старайся, никуда не всунешь, т.к. от программы клиента абсолютно ничего не зависит.
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
Неизвестный
04.06.2009, 16:45
общий
Foxbox:
Будет гораздо быстрее работать, чем пересылать "банковские транзакции" на клиентскую часть

Ужос, кто Вам сказал что это проходит через клиента? Да он просто не осилит этого и сеть всю посадит. Нет, естественно все делается на сервере, но запускает то этот процесс не админ с консоли сервера - а клиент, и о ходе результата оно получает уведомления - а т.к. этот процесс затяжной ( нередко когда такие запросы оставляют работать на ночь) то и естественно что они в отдельных потоках. К тому же, надо иметь средства управления ходом процесса, а это выполнить не реально, если приложение будет занято.
Неизвестный
04.06.2009, 17:16
общий
Цитата: 19206
Ужос, кто Вам сказал что это проходит через клиента? Да он просто не осилит этого и сеть всю посадит

Diasoft 5NT Forever! Начисления (особенно скриптовые) - вперед к победе коммунизма! Другое дело, что в данной системе реализованы и другие способы обработки данных... И вот сидят юзвери в банках и смотрят на мертвый экран. Я не хочу у себя подобное вытворять. Кстати, сеть-то как раз не посадится. Туда-сюда гонять маленькие запросики после действительно большого чтения данных - сеть от запросиков не посадится.

Многопоточность, как ни старайся, никуда не всунешь, т.к. от программы клиента абсолютно ничего не зависит.

Через многопоточность можно попытаться обойти зависание программы во время исполнения квери, оповещая, что "я ещё живая" например в виде счетчика времени пройденного от момента начала исполнения запроса, причем счетчик показывать после 3-х секунд (ну нет смысла поднимать окошко при каждом исполнении запросов).

Ну а логирование... логирование нужно разработчику. пользователь шкрябает у себя по киборду, но вдруг машина у него стала спотыкаться. берешь файл логов, который автоматом создается по ходу работы, с машины юзверя и смотришь, что и как отработало... просто как медовый пряник. особенно, если обеспечиваешь перехват принтов (print MSSQL) из серверных процедур и в лог-файл засовываешь. Вообще красота! Косяки собственные тут же видны.
Форма ответа