Возможно ли для потока, который не является потоком пользовательского интерфейса, манипулировать элементами пользовательского интерфейса? - PullRequest
0 голосов
/ 29 июня 2018

Я прочитал, что только потоку пользовательского интерфейса разрешено манипулировать элементами пользовательского интерфейса в WinAPI. Но я не думаю, что поток, который не является потоком пользовательского интерфейса, даже может манипулировать элементами пользовательского интерфейса.

Я думаю, что, поскольку, когда поток (который не является потоком пользовательского интерфейса) вызывает функцию SendMessage () для управления каким-либо элементом пользовательского интерфейса, сообщение будет отправлено в поток пользовательского интерфейса, а затем поток пользовательского интерфейса, который будет управлять элементом пользовательского интерфейса, а не другим потоком.

Я прав?

1 Ответ

0 голосов
/ 08 июля 2018

Во-первых, гипотетически говоря, пытаясь удовлетворить любопытство ОП:

  • Если мы определяем манипулирование элементами пользовательского интерфейса как чтение или запись в свойства элементов, то технически вы можете придумать свой собственная структура пользовательского интерфейса, которая будет поддерживать элементы независимо от Windows API. Такие попытки были сделаны . WPF является одним из их. Затем вы могли бы теоретически сделать рамки потокобезопасен и позволяет получить доступ к свойствам элементов из нескольких потоков.
  • Кроме того, GDI разрешает доступ к своим объектам из нескольких потоков, так что вы потенциально может привлечь к вашему окну из нескольких потоков (то же самое для DirectX). WPF, например, имеет выделенный поток рендеринга (или в хотя бы раньше). Вы также можете указать другой поток ввод процесса с AttachThreadInput.

Однако, учитывая предпосылку вопроса о том, что мы придерживаемся использования стандартного Windows API для создания и управления пользовательским интерфейсом, можно с уверенностью сказать, что доступ к окну осуществляется только из потока, который его создал, потому что SendMessage() переключится на поток владельца. Но это не значит, что вызов SendMessage() из нескольких потоков является безопасным или рекомендуемым подходом. Напротив, это чревато опасностью, и нужно быть внимательным, чтобы правильно синхронизировать потоки.

С одной стороны, типичный WndProc() выглядит так:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    ...
    switch (message)
    {
        case WM_MYMSG1:
            ...
            SendMessage(hWnd, WM_MYMSG2, wParam, lParam);
            ...
        break;
        ...    
    }
    ...
}

Таким образом, чтобы защитить ваш WndProc(), чтобы к нему можно было получить доступ из нескольких потоков, вы должны обязательно использовать блокировку с повторным входом, а не семафор, например.

Во-вторых, если вы используете блокировку повторного входа, вы должны убедиться, что она используется только в пределах WndProc() или даже сделать ее специфичной для сообщения. Иначе очень легко попасть в тупик:

//Worker thread:
void foo () 
{
    EnterCriticalSection(&g_cs);
    SendMessage(hWnd, WM_MYMSG1, NULL, NULL);
    LeaveCriticalSection(&g_cs); 
} 

//Owner thread:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        case WM_MYMSG1:
        {
            EnterCriticalSection(&g_cs); //Deadlock!
            ...
            LeaveCriticalSection(&g_cs); 
        }
        break;
    }
}

В-третьих, вы должны быть уверены, что не вызовете какие-либо управляющие функции в вашем WndProc(); включают, но не ограничиваются : DialogBox(), MessageBox() и GetMessage(). Иначе вы зашли в тупик.

Затем рассмотрим многооконное приложение, в котором насос сообщений каждого окна запускается в отдельном потоке. Вы должны быть уверены, что не будете отправлять сообщения между потоками, чтобы не привести к тупику:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    ...
    switch (message)
    {
        case WM_MYMSG1:
            ...
            SendMessage(hWnd2, WM_MYMSG1, wParam, lParam); //Deadlock!
            ...
        break;
        ...    
    }
    ...
}

Вы также должны быть очень осторожны с использованием API-интерфейсов Windows, которые неявно управляют специфичными для процесса блокировками операционной системы, а сохраняет и поддерживает правильную иерархию блокировок . В эту категорию попадает несколько функций User32, и многие блокирующие вызовы COM.

Некоторые из этих проблем можно устранить с помощью InSendMessage() и ReplyMessage() (при использовании SendMessage()) или PostMessage() и его родных элементов. Однако затем вы попадаете во все виды проблем потока управления, потому что вам может потребоваться узнать, что сообщение было обработано перед продолжением текущего потока или обработкой следующего сообщения. Таким образом, в конечном итоге вам все равно придется реализовать какой-то механизм синхронизации, но это становится все труднее, и нужно избегать многих подводных камней.

Проблемы не ограничиваются просто отправкой сообщений между потоками. Изменение WndProc() из другого потока может привести к ужасным ошибкам в состоянии гонки :

//in UI thread:
wpOld = (WNDPROC)GetWindowLongPtr(hwnd, GWLP_WNDPROC);
//in another thread:
SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)otherWndProc);
//back in UI thread:
SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)newWndProc);
//still in UI thread:
LRESULT CALLBACK newWndProc(...)
{
    CallWindowProc(wpOld, ...); //Wrong wpOld!
}

Кроме того, неправильное использование контроллеров домена из нескольких потоков может привести к незначительным ошибкам .

Эти и другие причины (включая производительность), возможно, побудили разработчиков стандартных оболочек API, таких как MFC и WinForms, просто предположить, что их API будут использоваться в однопоточном контексте. Они не предлагают никаких средств защиты потоков, и пользователь может реализовать такие механизмы, однако более высокий уровень абстракции делает его еще проще до , игнорируя основные проблемы . Когда возникают такие проблемы, обычно ответ таков: не используйте элемент управления извне потока владельца.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...