Завершение потока
От: Hrum  
Дата: 14.09.02 07:29
Оценка:
Господа,прошу помощи...
Проблема такая:
Есть приложение на базе SDI каркаса MFC где класс вид содержит в себе несколько диалоговых
окон.Одно из таких окон содержит в качестве переменной указатель на объект класса CListCtrl,
который создается в конструкторе.
Заполнение этого списка происходит в отдельном потоке(поскольку количество элементов очень
велико,а блокировать интерфейс плохо).
Поток создается функцией AfxBeginThread,которой в качестве параметра пересылается указатель
на список.
Вопрос:как корректно завершить поток досрочно, если пользователь решил закрыть
приложение.
Пытался сделать следующим образом:
Создавал обект CEvent g_eventStop,в цикле потока,
перед очередным занесением элемента в список,делал проверку типа
if(WaitForSingleObject(g_eventStop,0)==WAIT_OBJECT_0) return 0;
А в функции OnClose диалога:
CMyDialog: nClose()
{
g_eventStop.SetEvent();
WaitForSingleObject(хендл на поток,INFINITE);
CDialog: nClose();
}
Все работает,если сделать просто пустой цикл,если же вставлять там новый элемент в список,
то подвисаем.
Где ошибка и как исправить
Re: Завершение потока
От: Patalog Россия  
Дата: 14.09.02 07:48
Оценка:
Здравствуйте Hrum, Вы писали:

[skip]

H>Все работает,если сделать просто пустой цикл,если же вставлять там новый элемент в список,

H>то подвисаем.
H>Где ошибка и как исправить

Когда ты вставляешь элемент (InsertItem) идет SendMessage с LVM_INSERTITEM, а поскольку поток в Wait, сообщение не обрабатывается. Т.е. DeadLock, ибо SendMessage синхронный.
Почетный кавалер ордена Совка.
Re: Завершение потока
От: Patalog Россия  
Дата: 14.09.02 07:53
Оценка:
Здравствуйте Hrum, Вы писали:

[skip]

В догонку. Судя по всему ты юзаешь "The first form of AfxBeginThread", которая "creates a worker thread", т.е. без цикла обработки сообщений. Юзай "The second form", которая "creates a user-interface thread". Можно еще попробовать вместо InsertItem юзать PostMessage с LVM_INSERTITEM, ибо она не синхронная.
Почетный кавалер ордена Совка.
Re: Завершение потока
От: MaximE Великобритания  
Дата: 14.09.02 09:01
Оценка:
Здравствуйте Hrum, Вы писали:

H>Господа,прошу помощи...

H>Проблема такая:
H>Есть приложение на базе SDI каркаса MFC где класс вид содержит в себе несколько диалоговых
H>окон.Одно из таких окон содержит в качестве переменной указатель на объект класса CListCtrl,
H>который создается в конструкторе.
H>Заполнение этого списка происходит в отдельном потоке(поскольку количество элементов очень
H>велико,а блокировать интерфейс плохо).
H>Поток создается функцией AfxBeginThread,которой в качестве параметра пересылается указатель
H>на список.
H>Вопрос:как корректно завершить поток досрочно, если пользователь решил закрыть
H>приложение.
H>Пытался сделать следующим образом:
H>Создавал обект CEvent g_eventStop,в цикле потока,
H>перед очередным занесением элемента в список,делал проверку типа
H>if(WaitForSingleObject(g_eventStop,0)==WAIT_OBJECT_0) return 0;
H>А в функции OnClose диалога:
H>CMyDialog: nClose()
H>{
H>g_eventStop.SetEvent();
H>WaitForSingleObject(хендл на поток,INFINITE);
H>CDialog: nClose();
H>}
H>Все работает,если сделать просто пустой цикл,если же вставлять там новый элемент в список,
H>то подвисаем.
H>Где ошибка и как исправить

По поводу подвисания тебе уже объяснили.
Одно из самых простых решений избежать блокировки — посылать сообщения не синхронно, а асинхронно. Т.е. вместо SendMessage пользовать PostMessage (или SendNotifyMessage).
У меня была такая задачка. Был диалог с ListBox'ом. Этот ListBox заполнялся из других потоков. Вот как это было решено:

// test1Dlg.h : header file

#define WM_CUSTOM_ADDSTRING ( WM_APP )

class CTest1Dlg : public CDialog
{
public:
    CTest1Dlg(CWnd* pParent = NULL);    // standard constructor
    ~CTest1Dlg();
// ...
private:
    Talker *m_talker;
};


// test1Dlg.cpp : implementation file

BEGIN_MESSAGE_MAP(CTest1Dlg, CDialog)
// ...
    //}}AFX_MSG_MAP
    ON_MESSAGE( WM_CUSTOM_ADDSTRING, OnCustomAddString )
// ...
END_MESSAGE_MAP()


CTest1Dlg::CTest1Dlg(CWnd* pParent /*=NULL*/)
    :    CDialog(CTest1Dlg::IDD, pParent)
    ,     m_talker(0)

{
// ...
}

CTest1Dlg::~CTest1Dlg()
{
    delete m_talker;
}

BOOL CTest1Dlg::OnInitDialog()
{
// ...
    m_talker = new Talker( GetSafeHwnd(), IDC_LIST1 );
// ...
}

LRESULT CTest1Dlg::OnCustomAddString( WPARAM wp, LPARAM lp )
{
    m_talker->Answer( wp, lp );
    return 0;
}


// talker.h

class Talker { 
public:
    Talker( const HWND hwndDlg, const int nIDListBox );
    ~Talker();

    void Say( const TSTRING &str ) const;
    void Answer( const WPARAM wp, const LPARAM lp ) const;

private:
    const HWND        hwndDlg_;
    const HWND        hwndListBox_;
    const HANDLE    hHeap_;
    const int        nIDListBox_;
};


// talker.cpp

Talker::Talker( const HWND hwndDlg, const int nIDListBox )
    :    hwndDlg_( hwndDlg )
    ,    hwndListBox_( ::GetDlgItem( hwndDlg, nIDListBox ) )
    ,    hHeap_( ::HeapCreate( HEAP_GENERATE_EXCEPTIONS, 0, 0 ) )
    ,    nIDListBox_( nIDListBox )
{
    assert( hwndDlg_ );
    assert( hwndListBox_ );
    assert( hwndDlg_ );
}

Talker::~Talker()
{
    assert( hHeap_ );
    ::HeapDestroy( hHeap_ );
}

void Talker::Say( const TSTRING &str ) const
{
    if( ::IsWindow( hwndListBox_ ) )
    {
        TCHAR *strcopyinheap = (TCHAR *) ::HeapAlloc( hHeap_, HEAP_GENERATE_EXCEPTIONS, sizeof( TCHAR ) * ( 1 + str.size() ) );
        ::lstrcpy( strcopyinheap, str.c_str() );
        ::PostMessage( hwndDlg_, WM_CUSTOM_ADDSTRING, (WPARAM) strcopyinheap, 0 );
    }
}

void Talker::Answer( const WPARAM wp, const LPARAM lp ) const
{
    if( ::IsWindow( hwndListBox_ ) )
    {
        ::SendMessage( hwndListBox_, LB_ADDSTRING, 0, wp );
        ::HeapFree( hHeap_, 0, (PVOID) wp );
    }
}
Re[2]: Завершение потока
От: MaximE Великобритания  
Дата: 14.09.02 09:23
Оценка:
Здравствуйте MaximE, Вы писали:

Забыл сказать: потоки юзают метод Say() единственного объекта класса Talker (можно было реализовать как singleton, но это слишком для такой малой задачи).
Кроме того предпочтительнее пользовать в потоках именно асинхронные сообщения, т.к. тогда потоки не будут ждать пробужения потока окна и возврата из SendMessage.
Re[2]: Завершение потока
От: Jack Osicilator  
Дата: 14.09.02 13:20
Оценка:
Здравствуйте Patalog, Вы писали:

P>[skip].. PostMessage с LVM_INSERTITEM, ибо она не синхронная.


Однако будет очень внимателен когда передаешь указатели в PostMessage —
к тому времени когда контроль обратится к этой памяти ее содержимое может уже изменится!
Или память вообще может быть уже освобожденна.
Вообще-то эти сообщения (LVM_*) как раз должны использоваться с SendMessage,
а в твоем случае лучше использовать контроль в ввиртуальном режиме(LVS_OWNERDATA), который как раз и предназначен для отображения больших объемов данных:
Virtualizing List Views to Handle Large Amounts of Data
Люблю OSCилировать
Re[3]: Завершение потока
От: MaximE Великобритания  
Дата: 14.09.02 14:47
Оценка:
Здравствуйте Jack Osicilator, Вы писали:

JO>Вообще-то эти сообщения (LVM_*) как раз должны использоваться с SendMessage,

JO>а в твоем случае лучше использовать контроль в ввиртуальном режиме(LVS_OWNERDATA), который как раз и предназначен для отображения больших объемов данных:

LVS_OWNERDATA и многопоточность в данном контексте имеют очень сомнительную близость. При использовании LVS_OWNERDATA сообщения LVN_GETDISPINFO будут слаться в поток, создавший окно. А у человека контрол заполняется строками из другого потока.
Re[4]: Завершение потока
От: Jack Osicilator  
Дата: 14.09.02 21:17
Оценка:
Здравствуйте MaximE, Вы писали:

ME>LVS_OWNERDATA и многопоточность в данном контексте имеют очень сомнительную близость. При использовании LVS_OWNERDATA сообщения LVN_GETDISPINFO будут слаться в поток, создавший окно. А у человека контрол заполняется строками из другого потока.


LVS_OWNERDATA и многопоточность не только не близки, а вообше противопоставляются друг другу.
Когда используется LVS_OWNERDATA контрол не заполняется данными поэтому второй поток не нужен,
данные запрашиваются контролем только для тех строк которые будут отображены — и ни какого длительного копирования не надо.
Люблю OSCилировать
Re[5]: Завершение потока
От: MaximE Великобритания  
Дата: 14.09.02 22:19
Оценка:
Здравствуйте Jack Osicilator, Вы писали:

JO>LVS_OWNERDATA и многопоточность не только не близки, а вообше противопоставляются друг другу.

JO>Когда используется LVS_OWNERDATA контрол не заполняется данными поэтому второй поток не нужен,
JO>данные запрашиваются контролем только для тех строк которые будут отображены — и ни какого длительного копирования не надо.

Все правильно. Но чел спрашивал про многопоточность.
Re[6]: Завершение потока
От: MaximE Великобритания  
Дата: 14.09.02 22:21
Оценка:
Здравствуйте MaximE, Вы писали:

ME>Все правильно. Но чел спрашивал про многопоточность.


Челу нужно решение, а не общие слова. Для них есть раздел общее.
Re[7]: Завершение потока
От: Jack Osicilator  
Дата: 15.09.02 05:54
Оценка:
Здравствуйте MaximE, Вы писали:

ME>>Все правильно. Но чел спрашивал про многопоточность.


ME>Челу нужно решение, а не общие слова. Для них есть раздел общее.

Это альтернативное решение — многопоточность не единственный вариант, и даже может не лучший в данном случае.
Или вопрос именно о многопоточности, а не конкретно о заполнении ListControl-a?
Тогда почему в MFC форум?
Люблю OSCилировать
Re: Завершение потока
От: Vasiliy_Krasnokutsky Россия  
Дата: 16.09.02 05:53
Оценка: -1
Здравствуйте Hrum,
Уж если вы совсем программу собрались закрывать, то я думаю особенно то церемониться с нитью не стоит ...
AfxBeginThread возвращает указатель на нить (CWinThread*) и эту нить можно завершить просто и элегантно TerminateThread(Thread, 0);
А огород из мьютексов и прочего городить не стоит наверное
Re[2]: Завершение потока
От: Sergey Россия  
Дата: 16.09.02 08:57
Оценка:
Здравствуйте Vasiliy_Krasnokutsky, Вы писали:

VK>Уж если вы совсем программу собрались закрывать, то я думаю особенно то церемониться с нитью не стоит ...

VK>AfxBeginThread возвращает указатель на нить (CWinThread*) и эту нить можно завершить просто и элегантно TerminateThread(Thread, 0);

TerminateThread — это не есть просто и элегантно. Перед ее использованием стоит подумать на тему, "А что будет с программой, если поток, который я останавливаю с помощью TerminateThread, выполняет сейчас оператор new или функцию malloc?"
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Re[2]: Завершение потока
От: Аноним  
Дата: 16.09.02 10:20
Оценка:
Здравствуйте Vasiliy_Krasnokutsky, Вы писали:

VK>Здравствуйте Hrum,

VK>Уж если вы совсем программу собрались закрывать, то я думаю особенно то церемониться с нитью не стоит ...
VK>AfxBeginThread возвращает указатель на нить (CWinThread*) и эту нить можно завершить просто и элегантно TerminateThread(Thread, 0);
VK>А огород из мьютексов и прочего городить не стоит наверное

Ну по описанию ядра Windows эта функция самая неэлегантная
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.