2012-01-30 4 views
4

У меня есть приложение Delphi 6, которое сильно многопоточно. У меня есть компонент, который я создал, который происходит от TWinControl. Когда я его впервые построил, я использовал скрытое окно, и WndProc обрабатывал сообщения, выделенные AllocateHwnd(). Недавно я начал очищать WndProc в своем коде и решил удалить вспомогательный WndProc(). Я изменил компонент, чтобы вместо этого переопределить метод базового класса WndProc() и выполнить обработку пользовательских сообщений Windows. В этом WndProc() я сначала вызвал унаследованный обработчик, а затем обработал свои пользовательские сообщения (смещения WM_USER), установив для поля «Сообщение» значение «1», если нашел одно из моих пользовательских сообщений и обработал его.Как мог WndProc() Delphi 6 TWinControl выполнять потоки основного потока VCL?

Одно важное примечание. Я положил строку кода в верхней части переопределения WndProc(), которая генерирует исключение, если текущий идентификатор потока не является основным потоком VCL. Я хотел убедиться, что WndProc() выполняется только в контексте основного потока VCL.

После этого и запуска моей программы я столкнулся с чем-то, что кажется действительно причудливым. Я запускал свою программу как обычно и выполнял различные задачи без ошибок. Затем, когда я пошел в элемент управления TMemo, который находится на той же странице, что и мой потомок TWinControl. Если я щелкнул внутри, что TMemo контролирует проверку основного потока в моем переопределении WndProc(). У меня была точка останова на нем, и когда я пошел в стек вызовов, там ничего не было выше моего переопределения WndProc().

Насколько я могу судить, и я дважды проверял, я не делаю явных вызовов переопределения WndProc(). Это не то, что я когда-либо делал. Но учитывая, что мой компонент TWinControl был бы создан в основном потоке VCL, как и все остальные компоненты, я не могу понять, как переопределение WndProc() будет выполняться в контексте фонового потока, особенно когда действие UI, например произойдет щелчок мышью. Я понимаю, как мой WndProc() привязан к элементу управления TMemo, поскольку все дочерние окна висят от окна верхнего уровня WndProc(), по крайней мере, это мое понимание. Но так как все окна компонентов были бы созданы в основном потоке VCL, тогда все очереди сообщений тоже должны исполняться в этом контексте, верно?

Итак, какую ситуацию я мог бы создать, чтобы выполнить мой WndProc(), и только иногда, в контексте фонового потока?

ответ

5

Есть два способа WndProc() метод главным потоком компонента можно было бы назвать в контексте рабочего потока:

  1. рабочий поток непосредственно вызывает в WindowProc собственности компонента или его метода Perform().

  2. рабочая нить украла собственность на окно компонента из-за небезопасного использования имущества TWinControl.Handle. Handle свойство getter не является потокобезопасным. Если рабочий поток читает из свойства Handle в тот же самый момент, когда основной поток воссоздает окно компонента (TWinControl окна не являются постоянными - различные условия выполнения могут динамически воссоздавать их, не затрагивая большую часть вашей логики пользовательского интерфейса), тогда существует условие гонки, которое может позволить рабочему потоку выделять новое окно в его собственном контексте (и вызывать основной поток для утечки другого окна). Это приведет к тому, что основной поток перестанет принимать и отправлять сообщения в своем контексте. Если рабочий поток имеет свой собственный цикл сообщений, он будет принимать и отправлять сообщения вместо этого, тем самым вызывая метод WndProc() в неправильном контексте потока.

Мне кажется странным, что стек вызовов не создается.Всегда должна быть какая-то трассировка.

Кроме того, убедитесь, что переменная MainThreadId (или все, что вы используете для отслеживания основной темы) не просто повреждается случайно. Убедитесь, что его текущее значение соответствует его начальному значению при запуске.

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

+0

Спасибо. Я видел проблему с обработанным дескриптором, где из-за недействительной ссылки на окно, которую я перенесла один раз. Это одна из причин, по которой я инстинктивно занимаюсь AllocateHWND(), когда я не должен (я помню ваши комментарии к этому из другого потока). Есть ли способ в коде, чтобы я мог поймать рабочий поток, который захватывает дескриптор в простой вид? У меня много активности потоков, и я бы хотел быстро решить эту проблему, если бы не смог, без полной проверки кода. Что-то, что может вызвать исключение, когда поток попытался получить доступ к дескриптору, возможно? –

+3

попробуйте переопределить метод 'CreateWnd()' или 'CreateWindowHandle()' компонента и создать собственное исключение, если контекст вызывающего потока неверен. Но на самом деле, НИКОГДА не обращайтесь к свойству 'TWinControl.Handle' из рабочего потока для начала, если только он не обернут вызовом' TThread.Synchronize() '. –

+0

Спасибо. Это то, что мне нужно. –

1

Remy LeBeau ' содержит объяснение того, что я сделал неправильно. Я включаю это обновление, чтобы вы могли увидеть хитрые детали конкретного случая, который показывает, насколько может возникнуть тонкая ошибка, содержащая ссылку на элемент управления VCL UI в фоновом потоке. Надеемся, эта информация поможет вам отладить собственный код.

Часть моего приложения включает компонент VCL, который я создал, который спускается из TCustomControl, который, в свою очередь, спускается с TWinControl. Он объединяет сокет, и этот сокет создает фоновый поток для приема видео с внешнего устройства.

При возникновении ошибки этот фоновый поток отправляет сообщение контроллеру TMemo для целей аудита с использованием PostMessage(). Вот где я сделал свою ошибку, потому что дескриптор окна (HWND), который я использую с PostMessage(), принадлежит элементу управления TMemo. Элемент управления TMemo находится в той же форме, что и мой компонент.

Когда видеосвязь теряется, сокет, который обслуживает его, закрыт и уничтожен, но оказывается, что фоновый поток, обслуживающий его, еще не вышел. Теперь, когда сокет пытается выполнить операцию с неиспользуемым сокетом, с которым он ссылается, это приводит к ошибке сокета # 10038 (операция в не-сокете). Здесь начинается проблема.

Когда он вызывает PostMessage() с ручкой в ​​ТМето, тем TMemo находится в таком состоянии, что он должен воссоздать ручку по требованию, предательскую проблемы явление, которое описывает Remy. Это означает, что WndProc() в воссозданном окне TMemo теперь выполняется в контексте фонового потока.

Это соответствует всем доказательствам. Я не только получаю предупреждение о предыстории в моем переопределенном WndProc(), как упоминалось выше, но все, что сделано в окне TMemo с помощью мыши, вызывает поток сообщений об ошибках # 10038 в TMemo. Это происходит из-за того, что между TMemo, переопределенным компонентом WndProc() и фоновым потоком существует слабо связанное циклическое условие, поскольку этот поток имеет цикл GetMessage в методе Execute().

Каждый раз, когда сообщение Windows отправляется в элемент управления TMemo, например, из движений мыши и т. Д., Оно заканчивается в очереди сообщений фонового потока, так как в настоящее время оно принадлежит окну TMemo. Поскольку фоновый поток пытается выйти, и он пытается закрыть сокет на выходе, каждая попытка закрытия создает другое сообщение # 10038, которое должно быть отправлено в TMemo, сохраняя цикл, потому что каждый PostMessage() по существу является самозапуск ,

С тех пор я добавил метод уведомления к объекту, который управляет фоновым потоком, который сокет вызывает в своем деструкторе, позволяя потоку знать, что он уходит, и что ссылка неверна. Я никогда не думал об этом раньше, потому что гнездо отключает фоновый поток во время разрушения, однако я не жду события завершения из фонового потока.Альтернативным решением, конечно же, было бы ожидание завершения фонового потока. Заметьте, если бы я принял этот подход, тогда этот сценарий оказался бы в тупике вместо него, что привело бы к странному поведению с элементом управления TMemo.

[Примечание для редактора Stack Overflow - я добавляю эту деталь как ответ вместо модификации исходного сообщения, так что я не заставляйте ответ Реми, который содержит решение далеко вниз страницы.]

+1

Это действительно должно было быть опубликовано как редактирование в исходном сообщении, а не как его собственный ответ. Не беспокойтесь о размещении ответов других людей. –

+0

@RemyLebeau. Хорошо, я сделаю это в будущем. –