#gui #winforms #проектирование #lock_free #многопоточность
Интересуют реализации взаимодействия рабочих потоков с GUI со стороны рабочих потоков.
Например, загрузка файла с сервера выполняется в выделенном потоке. Этот поток должен
сообщать юзеру о прогрессе загрузки через какой-нибудь прогрессбар. Но доступ к прогрессбару
имеет только поток, владеющий родительским окном.
В .NET часто используются методы Control.Invoke() и Control.BeginInvoke()/Control.EndInvoke().
Этот вариант вполне годится, когда одна итерация фоновой задачи требует значительно
большего времени, чем отображение прогресса. Но он превращается в толстую прибавку
к длительности выполнения фоновой задачи, когда итераций много и время выполнения каждой
такой итерации сравнима с временем, которое рабочий поток тратит на уведомление о прогрессе.
Я в таком случае добавляю коллбек в lockfree-очередь и с помощью PostMessage уведомляю
об этом владеющий окном поток. В переопределенном методе WndProc последовательно выбираю
и выполняю все коллбеки из очереди. Таким образом, накладные расходы сводятся к минимуму.
Какие подходы применяете вы? Платформа и язык не важны, будут интересны все случаи.
Ответы
Ответ 1
Я в таком случае добавляю коллбек в lockfree-очередь и с помощью PostMessage уведомляю
об этом владеющий окном поток. В переопределенном методе WndProc последовательно выбираю
и выполняю все коллбеки из очереди. Таким образом, накладные расходы сводятся к минимуму.
У Вас самый хороший вариант. Только я бы добавил ограничение по времени на период
опроса очереди в WndProc, т.е. если lockfree-очередь не заканчивается к примеру через
100мс - завершать обработку текущего сообщения принудительно, чтобы избежать подвисаний GUI.
Миграция комментариев:
Я думаю, на самом деле такую проверку даже в WndProc делать нежелательно, поскольку
это означает, что на каждый элемент очереди синхронизации в очереди потока висит сообщение,
посланное post'ом.
--
Чтобы избежать холостой перегрузки очереди потока, лучше вырабатывать очередь lockfree
не по сообщению, а по событию (неавтомату [или семафору, в роли дросселя, это более
безопасно]) в каком-нибудь отдельном потоке, который выбрав из очереди синхронизации,
к примеру 100мс-узлов - отправит один post в очередь потока с этим, уже выбранным списком.
--
100мс-узлов - это число узлов, выбранное из очереди за не более 100мс.
--
Вобщем-то, в таком случае, очередь безболезненно заменяется на простой стек lockfree:
push - в прямом порядке, pop - в обратном, но последний push, который формирует очередь
100мс-узлов, опять вернет прямой порядок узлов.
--
Могу только опять заметить, что даже в такой ситуации от подвисания GUI гарантий
нет, т.к. мы ничего не знаем, что творится в самих коллбеках. Но вот этот момент и
можно уже ограничивать непосредственно в WndProc, отправляя "слишком долгие коллбеки"
на повторный круг - аналог post'а, только в очереди синхронизации.
--
После этого, повесить GUI сможет только уже один конкретный колбек, но это уже проблема
программиста, который решил все впихнуть в одну лямбду, к нему и претензии.
Дискуссия:
В ста миллисекундах 100000000 наносекунд; если задержка в десяток наносекунд, происходящая
раз в 100000000 наносекунд, мешает вашему алгоритму, что же говорить про context switch?
Во первых, @VladD, я не говорил о задержке раз в 100мс, а во-вторых, не говорил,
что она мешает. Она меняет логику работы алгоритма, работающего на lock-free-скоростях.
--
У меня есть такое понятие, как "ожидание свободного ресурса", т.е., если через какой-то
промежуток времени из стека lock-free не будет извлечен ресурс, то создается новый.
Вот я и нашел такой временнОй диапазон, который влияет на итоговый объем задействованного
ресурса задачей.
К примеру: при ожидании ресурса более 30нс - итоговое число ресурсов примерно ограничено
числом ядер(в моем случае - ~7-8), независимо от числа подзадач(~30-70 штук), его использующих,
а вот на 10нс, это число составляет примерно 70% от числа подзадач. Конечно, такие
задержки можно делать только на основе показаний TSC, о чем я сразу и сказал.
Вы уверены, что с вашим кодом всё в порядке?
Поверьте, у меня было достаточно времени его проверить :)
Или вы всё же немного приукрасили для большей выпуклости аргумента?
А Вы сами в это можете поверить? У меня хорошая репутация, зачем мне ее портить?
Ответ 2
Я лично пользуюсь Dispatcher.BeginInvoke в WPF (аналог Control.BeginInvoke). Низкоуровневые
штуки типа PostMessage кажутся мне хаком, никто не гарантирует, что окна в WPF всегда
будут основаны на WinAPI-шных окнах (Silverlight? WinRT?). Обновлять UI так часто,
чтобы аж затраты на обновление были существенны, не имеет смысла, так как всё равно
юзер не в состоянии осознать столь быстрые изменения. Поэтому я ограничиваю снизу время
между последовательными обновлениями прогресса, скажем, эмпирической сотней миллисекунд.
Kонцептуально более правильным решением было бы использование Rx Extensions для push-нотификаций,
но я пока не научился хорошо с ними работать.