С.Т. » 22.07.2025 (Вт) 23:47
Ура!!!!
Удалось!
Огромное спасибо Артуру за эту идею! И за чудесный финт с CreateMutex, и за таймеры, раскрывшие потокам дыхание! Я не сразу разобрался, там несколько запутано, но зато теперь у меня этот проект превратился действительно почти что в кирпич, сделанный на чистовик, с поддержкой IDE. Приложены пять примеров в порядке нарастания сложности:
1) самый простой с одним побочным потоком, который пыхтит в долгом напряжённом цикле, а форма спокойно занимается плавным сдвижением контрола;
2) то же самое с четырьмя потоками (слегка изменён проект для уверенной отладки нескольких потоков) – там можно запускать несколько потоков и любоваться в Диспетчере задач загрузкой процессора: 25%… 50%... 75%... четвёртый уже страшно нажимать, однако и при 99% форма на всё отзывается;
3) пример для вывода снимков веб-камеры в побочные потоки, чтобы задержка снимков не задерживала форму. Особенно яркий эффект наблюдается при плохих встроенных камерах в ноутбуке или при слабом освещении: в один поток вращение происходит ступенчато (перемежаясь с выдержкой кадров!), а при многопоточности - плавно.
4) Снимки с камеры увеличиваются. Компьютеру это сложно; линейная интерполяция занимает до 100 мс на кадр, отсюда неизбежные задержки вращения, т.к. увеличение происходит в основном потоке. Это пример-проблема. Она решена в следующем проекте с помощью многопоточности:
5) Увеличение происходит в побочном потоке сразу после съёма кадра и передаётся в основную форму по указателю на данные спрайта. Вращение происходит плавно, проблема решена!
Преимущества:
1) Вот именно что можно ОТЛАЖИВАТЬ многопоточную программу без проблем. И по F8 можно следить команда за командой, и в любой момент жмём в IDE «Стоп» - все живые потоки корректно завершаются командами "End" в своих классах (можно добавить предварительное закрытие необходимых дыр в индивидуальных случаях). В данном проекте в режиме Отладки циклы автоматически превращаются в таймеры, и все потоки превращаются в фиберы и распределяются равномерно, никто не перетягивает одеяло на себя (как происходит в циклах с DoEvents, которые невозможно отлаживать, т.к. всё наперекосяк). Разумеется, интерфейс форм при IDE с нагруженными потоками работает медленно и ступенчато, но при отладке это даже удобнее.
2) Такую программу невозможно уронить и сложно повесить, нужно допустить серьёзную алгоритмическую ошибку. Просто так она никогда не вылетит. А накладные расходы на закулисный маршалинг при нынешних процессорах можно даже не принимать во внимание. Проверил передачей переменной Timer!: передача кадра с камеры в другой поток длится 0 (изредка моргает 0,00078) секунд. И где пресловутые "задержки" между потоками? Их нет. Обратный эффект я поимел в многопоточных StandardEXE по окольным путям, потому что многие OCX и DLL имеют собственную многопоточность и с любой обёрткой CreateThread'a тут же начинают драться: кое-как на сотый вариант сложишь, чтобы процесс не развалился, потом что-то поправишь - опять падает... А тут напротив: всё напрямик шуруешь из потока в поток, а он глотает и гладко работает.
Ограничения:
1) Этот проект работает корректно, если использовать многопоточность по назначению, а именно: отсылать в отдельный поток большие нагрузные задачи и глухие циклы с редким (5-10 раз в секунду) возвратом результата. Пока выполнялся цикл в отдельном потоке, форма работала, отзывалась, плавно перемещала элементы и т.д. без задержек. Если же в отдельном потоке слишком простая задача и он слишком часто передаёт результат (сотни раз в секунду), мы не заметим особой разницы с однопоточностью, поскольку в миг передачи результата поток замораживается. Но зачем простая задача в отдельном потоке? Поток на то и создаётся, чтобы выполнять длинный сложный цикл. И вот когда передача ответа от потока производится даже 10 раз в секунду, уже в полной мере ощущается многопоточность – запускается почти два ядра (на четырёхъядерном нагрузка вместо 25% составляет 40 - 45%). Почему ПОЧТИ два? Потому что процесс ПЕРЕДАЧИ результата происходит только в основном потоке, и второе ядро оказывается чуть-чуть недогружено. Но если поток передаёт ответ 1 раз в секунду, процессор нагружен уже на полные 50%, и мы видим те же результаты, что и при полноценной многопоточности.
В тестовых проектах обычно пишут в побочный поток: «Do: i=i+1: MainForm.Answer i: Loop While Doing». В таком случае второе ядро совершенно не задействуется, процессор показывает 25%, потому что второму потоку делать нечего – всё уходит только на передачу ответа. И нам кажется, что многопоточность не работает (как же, у нас же глухой цикл! – Нет, не глухой, Answer его разряжает). Но стоит перед вызовом «Answer» запустить паразитный цикл «For n=1 to 99999999: Next», как второе ядро оживает на глазах! Оно занято циклом For целую секунду! И мы видим 50% и настоящую многопоточность! И кто сказал, что встроенная многопоточность VB6 не позволяет обмениваться информацией? Отлично всё передаётся даже напрямую, через ссылку на класс потока, и можно передавать даже объектные ссылки (As Variant с дальнейшим вызовом по точке вслепую) - при обращении по таким ссылкам ПОТОК НЕ ЗАМОРАЖИВАЕТСЯ. (Ну, точнее, замораживается только если неверно что-то сделать.)
2) Хотя между потоками спокойно передаются все типы данных и даже формы (через Variant), но пока не получилось у меня передать между потоками объект стороннего класса. В моём случае спрайт SprMax класса SR2D.
Если обращаться к ThreadClass.SprMax напрямую, всё сливается в один поток.
Если в Event от ThreadClass'a поставить Set SprMax = SprMax _, сольётся при дальнейшем обращении.
Если SprMax.LoadFromSprite SprMax _, пишет «Cannot call Friend function…»
Значит, свойство Instancing класса SR2D надо делать – 1.Private. Но тогда полученную ссылку из другого потока он считает не принадлежащей своему классу и выдаёт Type mismatch. Через Variant или Set никакие преобразования не получаются.
ЕДИНСТВЕННЫЙ НАЙДЕННЫЙ ВЫХОД без хаков - передавать не объект класса, а его данные. В нашем случае - это DataPTR и Width*Height. А в другом потоке принимать эти данные обратно в класс (спрайт) через CopyMemory. Операция занимает ничтожное время (от 0 до 4 мс для полноэкранной картинки), так что выход адекватный. В примере №5 так всё и реализовано: спрайт увеличивается в отдельном потоке, а в форму передаёт указатель на свои данные, где конвертируется обратно в спрайт.
3) ActiveX EXE требует прав администратора при ПЕРВОМ запуске. Имя проекта задано трудноповторимое, и если другого проекта с тем же именем не запускать, то все дальнейшие запуски делаются без подтверждения и с обычными правами. Ну, раз уж программа доросла до многопоточности, значит наверняка уже в ней всякие OCX и DLL, и она всё равно требует установки, во время которой и будет произведен первый запуск от админа. Не говоря о том, что на многих компьютерах не работает msvbvm60, и любой VB6-программе однозначно требуется инсталляция.
Ещё такая программа не любит перемещения файла EXE, но это и всегда не приветствуется для инсталлированных программ.
4) Все MsgBox в побочном потоке нужно делать с флагом vbSystemModal, а иначе эти побочные MsgBox будут прятаться за главной формой и вешать поток.
Особенности:
В данной редакции проекта почти все Event'ы, вечно требующие внимания и синхронизации, упразднены и заменены на простые вызовы процедур. Проверял по диспетчеру и на глазок – никакой разницы в быстродействии не заметил: миг передачи параметра от потока к основной форме и так и так блокирует поток, дополнительных же расходов не заметил. А программировать гораздо удобнее, когда напрямую пишешь в одном потоке функцию или общую переменную, а из другого тут же её читаешь или меняешь, не городя паутину Event'ов. Всё равно он любые передачи маршализует.
Из потока спускать переменную в основную форму можно, по-моему, как угодно, а вот из основной формы не стоит лишний раз обращаться к классу потока, т.к. это подтормаживает всю форму на одну итерацию цикла потока, но этого, по-моему, не избежать. Вместо этого лучше из потока посылать переменные в основную форму. А вызов из осн.формы процедур потока - дело довольно редкое и обычно происходит либо в начале/конце потока, либо в момент нажатия пользователем, когда задержка незаметна.
У вас нет доступа для просмотра вложений в этом сообщении.