Вот хотел проконсультироваться насчет следующей задачи.
Имеется некая процедура, в теле которой вызывается другая процедура, которая работает в отдельном потоке.
Для того чтобы не зависал гуй. Т.е. мне надо дождаться окончания работы потока и продолжить код главного потока.
Начал делать на TEvent, но не понравилось мне это и реализовал так:
...
Thread.Start;
while not Thread.IsOk do
begin
Application.ProcessMessages;
end;
IsOk - это свойство потока, которое устанавливается в true, когда завершается Execute
Вроде всё работает, но опять не нравится мне это.
Вот и хочу спросить: как поступать правильно?
Уважаемые авторы вопросов! Большая просьба сообщить о результатах решения проблемы на этой странице. Иначе, следящие за обсуждением, возможно имеющие аналогичные проблемы, не получают ясного представления об их решении. А авторы ответов не получают обратной связи. Что можно расценивать, как проявление неуважения к отвечающим от автора вопроса.
27-10-2019 10:00 | Сообщение от автора вопроса
Спасибо всем.
Первый вариант отдал в работу, но начал делать "правильную" версию. Обязательно учту Ваши замечания.
Получается примерно такая картина: поток нужен исключительно для того, чтобы можно было двигать форму приложения, но на неё всё-равно нажать ничего нельзя (включая кнопку: "Я передумал качать этих интернов через GPRS в роуминге, можно отменить?") Если так - то предлагаю переделать немного визуальную часть приложения. Нажатие на кнопку должно запускать поток, но менять название кнопки с "Начать закачку" (например! может быть и "Начать расчёт") на "Прервать закачку" (соответственно, "Остановить расчёт, потеряв данные").
При нажатии кнопки нужно проверять состояние запущенности потока: либо то, что Thread<>nil, либо Caption кнопки (худшее из всех вариантов), либо Tag кнопки, либо какой-то отдельный флаг. Соответственно, нажатие кнопки, когда процесс НЕ запущен просто запускает поток на исполнение (возможно, инициализирует поток, или что-то такое). Это не займёт много времени, скорее всего, пользователь даже не заметит, что что-то произошло (кроме смены Caption кнопки), но для верочки можно выполнить такой финт ушками:
Screen.Cursor:=crHourglass;
try
.. // ваш код по запуску/завершению потока
finally
Screen.Cursor:=crDefault;
end;
Финт заключается в том, что курсор-часики будет "крутиться" даже если ваше приложение в принципе зависло, он крутится силами самой ОС, а не вашего приложения.
Приём сообщения от потока о нормальном завершении приведёт к тому, что кнопка переименуется штатно обратно в "Начать закачку", поток штатно завершится и все флаги (заголовок, Tag кнопки, Thread:=nil, или ещё какой-то флаг) будут сняты, приготовив программу к новой итерации.
Если же в нажатии кнопки выяснится, что поток запущен, то надо ему послать сообщение: "Надо помереть" и выполнить WaitFor (если потоки выполнены не на базе TThread и вы пользуетесь Windows - то WaitForSingleObject). В данном случае - согласен, интерфейс "заморозится" на некоторое время, но тут ничего и не сделаешь нормально, только ранее описанный финт ушами. Я думаю, такое решение вас должно устроить. WaitFor... должен подождать некоторое разумное время (я обычно ставлю 3...30 секунд, в зависимости от задачи), после чего (если не дождался) - выполнить TerminateThread и доложить пользователю. Разумеется, при любом раскладе, надо также, как и при нормальном завершении потока снять флаги и поменять Caption кнопарика.
Хотя если хочется сложностей - можно сделать флаг не булевый, а целочисленный (тогда - либо Tag, либо внешний флаг), тогда можно будет по второму нажатию кнопки перевести её в состояние "Завершаемся... подождите..." В этом случае, WaitFor... мы не используем, поток сам доложит через сообщение в основную программу о том, что произошло. И вновь может оказаться, что поток сам не завершается. В этом случае, если кнопка прожимается в состоянии "Ждём завершения потока", надо будет выполнить TerminateThread... ну и далее - как в предыдущем пункте. Хотя есть вариант дать пользователю кнопку "Возобновить процесс". Этот вариант требует самого аккуратного программирования, так как количество состояний данного "конечного автомата" уже четыре, да ещё и синхронизация с дочерним потоком...
>>>как мне дождаться завершения потока?
Так я же написал. Запускаем поток и возвращаемся. Чтобы пользователь не тыкал куда не надо, вешаем форму-заглушку ожидания с какой-нибудь крутилкой, запрещаем формы/контролы, ждем от потока сообщения о завершении. Потом все размораживаем.
>>>То же самое с ожиданием вставки флэшки. Хотя, это можно было бы вставить в модальное окно, но испортило бы задуманный интерфейс приложения.
Модальное окно и есть верный вариант. Но можно и основное окно заморозить (Enabled:=false), показав в интерфейсе какое-то напоминание. Но вообще если пользователь по ходу процесса должен вводить какие-то данные, то напрашивается что-то вроде визарда
Хотел добавить, что в этой же проге возникла похожая задача. Потребовалось вставить в "прямоленейный код" выбор данных из Combobox. Опять цикл с ProcessMessages и проверкой совершения выбора? То же самое с ожиданием вставки флэшки. Хотя, это можно было бы вставить в модальное окно, но испортило бы задуманный интерфейс приложения.
Спасибо. Конечно, уже написано миллион статей про это. Но тут получилось, что в готовый код пришлось встраивать многопоточность в очень срочном порядке. Сам код - это, как бы последовательность команд. Какие-то команды выполняются в отдельных нитях (чтобы не висло главное окно). Т.е. вызвал процедуру и нужно ждать её завершения. При этом окно прорисовывается при перекрытии. Контролов минимум и вроде всё блокирую, чтобы не нажать и не создать лапшу. ProcessMessages сам считаю злом, поэтому и написал.
Но вот в данном случае, чтобы не переписывать всю программу, как мне дождаться завершения потока? Даже если посылать сообщения, то они будут обрабатываться в одном месте, а выполнение надо остановить в другом. Цикл без ProcessMessages? Но тогда окно не прорисовывается.
Короче, нехорошо написал, но переделать нет возможности. :(
Избегайте ProcessMessages. Захочется приложению закрыться в этом цикле, что будете делать?
Пусть поток пошлет сообщение главному окну (через PostMessage) о том, что он завершил свои дела. Так что после старта потока просто возвращайтесь к основной работе
Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter. Функция может не работать в некоторых версиях броузеров.