#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-нотификаций, но я пока не научился хорошо с ними работать.
Комментариев нет:
Отправить комментарий