2017-02-16 4 views
1

Как и в заголовке, я хочу добавить/удалить элементы класса, полученного из класса WTL CListViewCtrl, из рабочих потоков, но всегда получаю «Необработанное исключение: нарушение прав доступа к чтению».Обновление CListViewCtrl из рабочих потоков в WTL и C++

Я попытался Win32 API PostMessage и SendMessage но когда рабочий поток коснется HWND из CListViewCtrl я получаю такое же исключение.

// CListCtrl member function, calling from worker thread 
HWND GetHwnd() 
{ 
    return hwndListCtrl;  // exception here 
} 

Я это SafeQueue, но когда рабочий поток затрагивает мьютекс или очередь, то исключение попробовал еще раз.

// SafeQueue is member variable in CListViewCtrl, created in GUI thread 
SafeQueue<T> m_SafeQueue; 
. . . 
// member function in SafeQueue class, calling from worker thread 
void enqueue(T t) 
{ 
    std::lock_guard<std::mutex> lock(m); // exception here 
    q->push(t); 
} 

Я попытался создать мьютекс и очередь с новый и HeapAlloc/LocalAlloc но тем же исключением снова.

Я пробовал Win32 API CreateMutex, но не повезло, это же исключение при доступе к дескриптору мьютекса из рабочего потока.

Он отлично работает, когда я добавляю элементы из потока графического интерфейса.

Единственный способ это работает из рабочих потоков, если я объявляю HWND или мьютекс и очередь в статической/глобальной, но я хотел бы избежать этого, так как я хочу использовать более одного экземпляра из этого ListControl и я предпочитаю любой более изящный способ, чем глобальная переменная.

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

Я ценю любую помощь и идею, как я могу это сделать.

Окружающая среда: VS2015 сообщество, WTL/C++ и Win10 Pro 64bit

Я нашел проблему, которая вызывает нарушение прав доступа: Я объявил ThreadProc функции обратного вызова в качестве функциистатического члена в классе CListViewCtrl.

// DO NOT USE 
// in CListViewCtrl 
**static** DWORD WINAPI ThreadProc(LPVOID lp) 
{ 
. . . 
} 

LRESULT OnStartWorkerThread(WORD /*wNotifyCode*/, WORD /*wID*/, HWND . ..) 
{ 
    DWORD dw; 
    ::CreateThread(NULL, 0, this->ThreadProc, NULL, 0, &dw); 
} 

рабочий раствор:

class CListViewCtrl ... 
{ 
    // thread-safe queue to store listctrl items to be added later in GUI thread 
    SafeQueue<CListCtrlItem<nCols> > m_SafeQueue; 

    // thread ID of the thread in which listctrl was created, saved in OnCreate 
    DWORD m_dwGuiTid; 

    // . . . 

Проверить, если SafeAddItem функция вызывается из GUI или любых других потоки

BOOL InvokeRequired() 
    { 
     if (m_GuiTid == ::GetCurrentThreadId()) 
      return false; 

     return true; 
    } 

    // ... 

SafeAddItem функции-члена может быть вызвана из графического интерфейса и рабочего нити

void SafeAddItem(CListCtrlItem<nCols> item) 
    { 
     if (!InvokeRequired()) 
     { 
      // we are in GUI thread so just add listctrl item "normal" way 
      AddItem(item); 
      return; 
     } 

    // we are in other thread so enqueue listctrl item and post a message to GUI   
     m_SafeQueue.Enqueue(item); 
     ::PostMessage(m_hWnd, WM_ADD_ITEM, 0, 0); 
    } 
    // . . . 

Обработчик сообщений из PostMessage, мы в GUI потоке

LRESULT OnAddItem(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled) 
    { 
     CListCtrlItem<nCols> item; 
     while (!m_SafeQueue.Empty()) 
     { 
      item = m_SafeQueue.Dequeue(); 
      // we are in GUI thread so we can add list ctrl items normal way 
      AddItem(item); 
     } 
     return 1; 
    } 
    // ... 
} 

И теперь мы можем добавлять элементы ListCtrl из любых потоков таким образом.Я прохожу этот указатель ThreadProc в _beginthreadex

m_ListCtrl.SafeAddItem(item); 

ответ

0

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

У вас аналогичная проблема в WIN32.

Существует прекрасная статья на эту тему, на которую я обращу ваше внимание. В нем также рассматриваются различные безопасные обходные пути: https://www.codeproject.com/Articles/552/Using-Worker-Threads

рабочих потоков и GUI II:.. Не касайтесь графического интерфейса пользователя

«Это право Рабочий поток не должен касаться объекта GUI Это означает, что вы не должны запроса состояния элемента управления, добавить что-то в окно списка, установить состояние управления и т.д.

Почему?

Потому что вы можете попасть в серьезную тупиковой ситуации. классический пример был размещен на один дискуссионных советов, и он описал что-то, что произошло с я в прошлом году. Ситуация такова: вы начинаете поток, а затем решаете дождаться завершения потока. Тем временем поток делает что-то явно безобидное, например, добавляет что-то в поле списка, или, в примере, который был отправлен, вызывает FindWindow. В обоих случаях процесс пришел в ступор, так как зашли в тупик все нити.»

+0

Благодарим вас за ответ.Я знаю, что мне не нужно обращаться к объектам GUI из другого потока, но я не мог получить доступ даже к дескриптору мьютекса, что важно для синхронизации между потоками. И доступ к элементам данных из другого потока не должен приводить к исключению нарушения прав доступа только по странному поведению. У меня возникло ощущение, что это странная проблема, и похоже, что я нашел проблему: я объявлял функцию обратного вызова рабочего потока как статическую функцию-член в CListViewCtrl. А статическая функция-член может получить доступ только к статическому элементу данных. – hkhk

+0

Спасибо за разъяснение. Из вашего описания - «Я хочу добавить/удалить элементы в класс, полученный из класса WTL CListViewCtrl из рабочих потоков ... Я пробовал Win32 API PostMessage и SendMessage, но как только рабочий поток касается HWND CListViewCtrl, я получаю то же исключение. Это нормально работает, когда я добавляю элементы из потока графического интерфейса. " Похоже, вы пытались перейти к ListView непосредственно из рабочего потока. –

+0

Извините, если это было непонимание. Сначала я попытался использовать PostMessage, но его первым параметром является целевое окно HWND, но я не смог прочитать/получить этот HWND из рабочего потока, поскольку это вызвало исключение нарушения прав доступа. Это звучало очень странно для меня, поскольку это не должно вызывать исключения. Теперь он работает так, как должен, с нечленой нестатической функцией ThreadProc. Спасибо – hkhk

1

вопрос, как представляется, на самом деле не о UI обновлений от рабочего потока, но и о правильном использовании рабочих потоков сами по себе.

Существует достаточное количество комментариев об опасностях, связанных с обновлением пользовательского интерфейса: все они связаны с потенциальной проблемой взаимоблокировки. Большинство обновлений связано с отправкой сообщения, которое является вызовом API блокировки. Пока вы выполняете обновление из рабочего потока и вызывающего потока заблокирован, любая попытка обработчика в пользовательском интерфейсе для синхронизации или совместной работы с рабочим может привести к тупиковой ситуации. Единственный способ обойти это - подготовить обновление в рабочем потоке и передать поток пользовательского интерфейса (в том числе путем размещения сообщения вместо этого его отправки, i n условий SendMessage, PostMessage API), чтобы взять и завершить обновления из потока пользовательского интерфейса.

К оригинальной проблеме: у вас, похоже, проблема со статической процедурой потока. The fourth argument in the CreateThread call is:

lpParameter [в, по выбору]

указатель на переменную, которые должны быть переданы нити.

У вас есть это NULL и вы, как правило, использовать его, чтобы передать this значение для вашей процедуры нить обратного вызова. Таким образом, вы можете передать исполнение назад от статической функции к экземпляру класса:

DWORD CFoo::ThreadProc() 
{ 
    // ThreadProc with proper "this" initialization 
    // HWND h = GetHwnd()... 
} 
DWORD WINAPI ThreadProc(LPVOID pvParameter) 
{ 
    return ((CFoo*) pvParameter)->ThreadProc(); 
} 
LRESULT CFoo::OnStartWorkerThread(WORD /*wNotifyCode*/, WORD /*wID*/, HWND ...) 
{ 
    DWORD dw; 
    ::CreateThread(NULL, 0, this->ThreadProc, (LPVOID) this, 0, &dw); 
} 

Также обратите внимание, что вы не должны использовать CreateThread прямо: у вас есть _beginthreadex и AtlCreateThread (related question).

+0

Спасибо за ваши предложения. Я знаю, что SendMessage является блокирующим вызовом, поэтому я хотел использовать PostMessage, но поскольку это была очень странная проблема, я пробовал все решения, чтобы заставить ее работать. Я обновил свой вопрос с помощью рабочего решения. Я использую этот SafeQueue для временного хранения элементов listctrl, поскольку было бы сложнее отправить указатели на поток в PostMessage wParam или lParam. – hkhk

Смежные вопросы