Re: Eliminate PumpMessage()
Quote:
Originally Posted by
zspirit
Following the principal, I am loading a large file in a worker thread and posting its data to the main thread. It is working beautifully but because the worker thread is busy in continuous reading loop, the main window still doesn't get a chance to update data and is not as responsive. My first thought was that it can still use PeekAndPump()!? but I added a Sleep(5) to the loop in worker thread and the GUI is much smoother now but the delay also adds to file loading as well with each iteration in the loop. I am going to try introducing delay after 50 iterations now instead of each one but am I am on the right track? I want to the main GUI to be very responsive and not waste much time with file loading as well. PeekAndPump() is out right? Is delay function the only way?
Seems like you are working on a machine with a single core processor. Those are becoming rare for PCs.
In any case, don't put delays in your worker thread. All it effectively does is limit the processor use of the worker thread. I think what you want is to run the worker thread in a lower priority. That way, your main thread will always get scheduled before the worker thread, thus making it more responsive.
How are you creating the worker thread?
Re: Eliminate PumpMessage()
I do have Core 2 CPU and tried it both on work as well as home PC (also Core 2), changed thread to priorities to THREAD_PRIORITY_BELOW_NORMAL, THREAD_PRIORITY_IDLE, THREAD_PRIORITY_LOWEST but I can never see a live filling of the list. Basically the windows show up and the list is empty (not updated) until full list is loaded. This rather defeat the purpose of the thread because I want to see the data which is already loaded by the file.
My objective is that even the file is very large and can take 30 seconds or more to load, the user should still start seeing the data right away and the rest of the list will be loaded in background while the user can work with already opened file.
Delay sound even worse than PeekAndPump() but how can I make this work without these?
Here is how I create the thread:
Code:
AfxBeginThread(LoadFileThread, (LPVOID) &tp, THREAD_PRIORITY_IDLE);
Re: Eliminate PumpMessage()
Try Sleep(0) which should cause thread context switch. I remember I did this in one of my old programs, could not imagine that this is still relevant on modern processors...
PeekAndPump may be used only in UI threads, I don't think it is relevant here.
Re: Eliminate PumpMessage()
Quote:
Originally Posted by
zspirit
I do have Core 2 CPU and tried it both on work as well as home PC (also Core 2), changed thread to priorities to THREAD_PRIORITY_BELOW_NORMAL, THREAD_PRIORITY_IDLE, THREAD_PRIORITY_LOWEST but I can never see a live filling of the list. Basically the windows show up and the list is empty (not updated) until full list is loaded. This rather defeat the purpose of the thread because I want to see the data which is already loaded by the file.
My objective is that even the file is very large and can take 30 seconds or more to load, the user should still start seeing the data right away and the rest of the list will be loaded in background while the user can work with already opened file.
Delay sound even worse than PeekAndPump() but how can I make this work without these?
Perhaps the problem is that your user interface thread is busy. How often are you posting update messages from your worker thread to your GUI thread (couple of times per second, thousand times per seconds,...)?
Also what do you do to handle the update message? If you are refilling a list box e.g. that could take a reasonable amount of time. If that has to happen a thousand times per second, your GUI thread will get clogged.
The fact that you don't see a "live filling" of the list could be because you need to Invalidate() the list box after you update it.
Re: Eliminate PumpMessage()
Quote:
Originally Posted by
D_Drmmr
Perhaps the problem is that your user interface thread is busy.
It has to fill the list and show the results so yes it is busy doing that however it is not excessive, just the regular load of filling a list with one row in each message posted.
Quote:
Originally Posted by
D_Drmmr
How often are you posting update messages from your worker thread to your GUI thread (couple of times per second, thousand times per seconds,...)?
I would say about thousand. The thread reads each record from file and post the data to the main thread, than it reads the next record and post data again and so on until all file is read. This is the loop. pseudo code
Code:
char * bfr = new 500;
file->Read(bfr, 500);
::PostMessage(hWnd , IDM_NEW_RECORD, (WPARAM) bfr, (LPARAM)0);
Quote:
Originally Posted by
D_Drmmr
Also what do you do to handle the update message? If you are refilling a list box e.g. that could take a reasonable amount of time. If that has to happen a thousand times per second, your GUI thread will get clogged.
The main thread responds to the message above by inserting the item in the Listview at next location (it only insert this item as rest are already there from previous thread messages). I think Invalidate() is automatically applied by InsertItem(). Even though one row is added in response to each message it still needs some amount of time to update the addition to listview/scrollbar visually. In my view this time probably increases as the items in the list increases as GUI has to manage more active data. But again I expect this is and it is what I am trying to smooth out so main thread can display up to date data and read thread keep digging it from file. Can't threads help out here?
Quote:
Originally Posted by
D_Drmmr
The fact that you don't see a "live filling" of the list could be because you need to Invalidate() the list box after you update it.
I thought additional Invalidate() could be excessive but I still tried that to see if changes anything but it doesn't.
Re: Eliminate PumpMessage()
Quote:
Originally Posted by
zspirit
It is said that true Win32 should never use a function like PeekAndPump() below to make it responsive, instead threads should be used.
Whoever said that is wrong. Threads have their advantage in certain situations but for little stuff like checking if a button is clicked or painting a window, a message pump is easier to use and works perfectly well.
Re: Eliminate PumpMessage()
Quote:
Originally Posted by
zspirit
I would say about thousand. The thread reads each record from file and post the data to the main thread, than it reads the next record and post data again and so on until all file is read.
[...]
The main thread responds to the message above by inserting the item in the Listview at next location (it only insert this item as rest are already there from previous thread messages). I think Invalidate() is automatically applied by InsertItem(). Even though one row is added in response to each message it still needs some amount of time to update the addition to listview/scrollbar visually. In my view this time probably increases as the items in the list increases as GUI has to manage more active data. But again I expect this is and it is what I am trying to smooth out so main thread can display up to date data and read thread keep digging it from file. Can't threads help out here?
The problem is that you are posting update messages faster than what is useful. No human being can track 1000 updates per second; 10 per second will already look quite "smooth". So instead of posting a message for every 500 bytes, do so for every 10000 bytes. That'll probably make your application more responsive.
Re: Eliminate PumpMessage()
Quote:
Originally Posted by
D_Drmmr
The problem is that you are posting update messages faster than what is useful. No human being can track 1000 updates per second; 10 per second will already look quite "smooth". So instead of posting a message for every 500 bytes, do so for every 10000 bytes. That'll probably make your application more responsive.
I appreciate your point of view but I would disagree here. If the main thread doesn't have time to process a single message (insert a single row) until the read thread is done, I don't know how can it find time to process inserting 500 records while the read thread is still in progress.
These updates are not alterations that they might be meaningless in a second but they are simply inserting new rows and I think it is easily noticeable by any user and in fact it is this noticeability that I am trying to make it work. I am trying to let the user at least see the list loading in action rather than almost not responding/no repainting and blocked like feeling.
Re: Eliminate PumpMessage()
Quote:
Originally Posted by
zspirit
If the main thread doesn't have time to process a single message (insert a single row) until the read thread is done
How did you deduce that? Cause the display isn't updated? That just means that the control isn't redrawn, not that your message is not handled. My guess is it's not getting redrawn, because the message pump is full of update messages before any repaint message comes in.
Quote:
These updates are not alterations that they might be meaningless in a second but they are simply inserting new rows and I think it is easily noticeable by any user and in fact it is this noticeability that I am trying to make it work.
Again, you won't notice the difference between 1 line being added 1000 times per second or 10 lines being added 100 times per second. That's biology (your eye/brain won't work that fast), it has nothing to do with programming. (Even your screen won't update 1000 times per second).
Just give it a try. It can't be that hard to program. :)
Re: Eliminate PumpMessage()
Re: Eliminate PumpMessage()
Quote:
Originally Posted by
D_Drmmr
Just give it a try. It can't be that hard to program. :)
I figured there is a short cut way I could try since you seem to have a point. In my test code I made the modification to post 1 message after 50 iterations rather than in each iteration so read thread is equally busy but the main thread does load the list live now :) So I guess I am a believer now :D Thank you! Now it makes my coding job a little more complicated..have to read bulk of records at once and pass/decipher!?
I was sending the message to view via hWnd from the thread and view was doing document management but is there a way I can access/manage document from the thread now to break the large chunk in pieces at the spot and place it in document?
Quote:
Originally Posted by
GCDEF
If you have thousands of records, you should be looking at a virtual list control.
I am already using custom draw and LVN_GETDISPINFO notification, that should do right or does owner draw does even more? my listview is not storing any data just a pointer to it.
Re: Eliminate PumpMessage()
Quote:
Originally Posted by
zspirit
I think Invalidate() is automatically applied by InsertItem(). Even though one row is added in response to each message it still needs some amount of time to update the addition to listview/scrollbar visually.
Although this may be true, the Invalidate() message is only processed when idle time permits.
You could try using the UpdateWindow() method on your list control after each time you insert a record.
Hope that helps.
Re: Eliminate PumpMessage()
Quote:
Originally Posted by
krmed
Although this may be true, the Invalidate() message is only processed when idle time permits.
You could try using the UpdateWindow() method on your list control after each time you insert a record.
Hope that helps.
Actually I had to remove the extra Invalidate() call I put in the message handler because it was excessive as I thought and caused flicker. It worked better without it.
Re: Eliminate PumpMessage()
Quote:
Originally Posted by
zspirit
I am already using custom draw and LVN_GETDISPINFO notification, that should do right or does owner draw does even more? my listview is not storing any data just a pointer to it.
There's more to it than that. It's been a while since I set one up so I don't remember the details, but from the sound of your problem, you don't have it implemented correctly.
Re: Eliminate PumpMessage()
Quote:
Originally Posted by
GCDEF
There's more to it than that. It's been a while since I set one up so I don't remember the details, but from the sound of your problem, you don't have it implemented correctly.
My implementation is that view receives data from read thread, adds it to the document and loads the list from document data. So I have all my data in one place in document class and I think it is quite in line with doc/view architecture. I would be interested to hear about anything in particular that might sound incorrect to you?
Re: Eliminate PumpMessage()
Quote:
Originally Posted by
zspirit
My implementation is that view receives data from read thread, adds it to the document and loads the list from document data. So I have all my data in one place in document class and I think it is quite in line with doc/view architecture. I would be interested to hear about anything in particular that might sound incorrect to you?
I was talking about your implementation of your virtual list control. As I said, it's been a while since I set one up, but there shouldn't be any significant time involved in loading a lot of records into one.
1 Attachment(s)
Re: Eliminate PumpMessage()
Check out the attached LVSelState project. It uses a virtual listview to display list items.
The user can change the number of virtual items up to 1M items. The virtual list control gets it's data from a std::list and the list is populated from a 2nd thread.
By default, the project waits until all the items are in the control until the control is refreshed, but you can change that behavior by commenting out the CWaitCursor and LockWindowUpdate calls.
Code:
HRESULT Populate( CListCtrl& ctlList )
{
HRESULT hr = S_OK;
// Add a wait cursor
// CWaitCursor wait;
long lIndex = 0;
LVITEM lvitem = { 0 };
int iItem = 0, iActualItem = 0;
lvitem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
// Lock the List Control window so it doesn't look
// like crap while filling
// ctlList.LockWindowUpdate();
// Lock the list
CAutoLockT< CLVItemDataList > lock( &m_LVItemDataList );
// Cycle through the list and add each item into the list control
for(CLVItemDataList::iterator it = m_LVItemDataList.begin();
it < m_LVItemDataList.end();
it++)
{
CLVItemData* pLVItemData = (*it);
lvitem.iItem = iItem;
lvitem.iSubItem = 0;
lvitem.lParam = reinterpret_cast<LPARAM>(pLVItemData);
// Tell the list control to become 'Virtual' and request display
// data from the lvitem.lParam.
// See CLVSelStateDlg::OnLvnGetdispinfoList for where this happens
lvitem.iImage = I_IMAGECALLBACK;
lvitem.pszText = LPSTR_TEXTCALLBACK;
lvitem.cchTextMax = MAX_LVITEMLEN;
// Insert the item
ctlList.InsertItem(&lvitem);
// Set the text for each column (all virtual callbacks)
ctlList.SetItemText(iItem, 0, LPSTR_TEXTCALLBACK);
ctlList.SetItemText(iItem, 1, LPSTR_TEXTCALLBACK);
ctlList.SetItemText(iItem, 2, LPSTR_TEXTCALLBACK);
iItem++;
// Check if user cancelled operation
if( WAIT_OBJECT_0 == WaitForSingleObject( GetShutdownEvent(), 0 ) )
{
return hr;
}
// Pump messages to allow cancel button click
// during control filling. Populating the control
// with thousands of records takes time, so we need
// to process messages to handle the cancel button
// and close messages
if(!ProcessMessages( ))
{
return hr;
}
// Restore the wait cursor
// (if you don't do this, the wait cursor may go
// away after processing messages)
// wait.Restore();
}
// Unlock the List control window
// ctlList.UnlockWindowUpdate();
return hr;
}
Re: Eliminate PumpMessage()
Quote:
Originally Posted by
zspirit
My implementation is that view receives data from read thread, adds it to the document and loads the list from document data...
Wait, you are not re-loading entire list after each record is read, are you?
Re: Eliminate PumpMessage()
Quote:
Originally Posted by
VladimirF
Wait, you are not re-loading entire list after each record is read, are you?
Definitely not, when the latest record/row becomes available from file I add it to the document and to the view. There is no refilling of the already loaded data.
Re: Eliminate PumpMessage()
The question is: do you or don't you use virtual list?
(And if you don't, you should do. The regular list control hardly can bear thousand objects without performance impacted.)
Re: Eliminate PumpMessage()
Thanks for the great demo, really appreciate it!
Quote:
Originally Posted by
Arjay
The virtual list control gets it's data from a std::list and the list is populated from a 2nd thread.
This caught my eye, I normally don't use std::list with listivew because link list can't be accessed via index unlike std::vector and your code indeed use std::vector as I expected.
Quote:
Originally Posted by
Arjay
Code:
ctlList.SetItemText(iItem, 0, LPSTR_TEXTCALLBACK);
ctlList.SetItemText(iItem, 1, LPSTR_TEXTCALLBACK);
ctlList.SetItemText(iItem, 2, LPSTR_TEXTCALLBACK);
I actually don't use this in my code because it is handled by LVN_GETDISPINFO handler anyways. I commented this in your project and works fine too without it.
Quote:
Originally Posted by
Igor Vartanov
The question is: do you or don't you use virtual list?
I think I might be confused with terminology here, more on this below. I took the opportunity to compared my project performance with Arjay's demo project. Here are the results.
My computer takes about 2 seconds to load/populate 7000 rows in Arjay's project.
My own project takes about 2.5 seconds to load 7000 rows from a file I have on disk.
Now the demo project as you see has only 3 columns and my listview has 35 columns. The demo project is not reading any disk file but I am also parsing 27MB file during this time. This makes me pretty happy about my results.
My loading mechanism is actually very similar to Arjay's project but I keep all the list data in CPtrArray instead of std::vector. Makes me think if I am already using virtual list without really know the terminology? :D All I know is I am definetely not using owner drawn listview style.
Re: Eliminate PumpMessage()
So what is the problem at this moment?
You do not get a visual feedback from the list that it is being populated?
Do you scroll your list to the bottom when new item is added? If not – the display is not going to change that much, just the scroll bar.
You do not need to Invalidate(), but, as was noted in one of the firs few replies, you need to call UpdateWindow() to enforce the update.
If it impairs performance, you can do it for every tenth, twenties or fiftieth update.
Re: Eliminate PumpMessage()
Quote:
Originally Posted by
zspirit
Thanks for the great demo, really appreciate it!
This caught my eye, I normally don't use std::list with listivew because link list can't be accessed via index unlike std::vector and your code indeed use std::vector as I expected.
I actually don't use this in my code because it is handled by LVN_GETDISPINFO handler anyways. I commented this in your project and works fine too without it.
I think I might be confused with terminology here, more on this below. I took the opportunity to compared my project performance with Arjay's demo project. Here are the results.
My computer takes about 2 seconds to load/populate 7000 rows in Arjay's project.
My own project takes about 2.5 seconds to load 7000 rows from a file I have on disk.
Now the demo project as you see has only 3 columns and my listview has 35 columns. The demo project is not reading any disk file but I am also parsing 27MB file during this time. This makes me pretty happy about my results.
My loading mechanism is actually very similar to Arjay's project but I keep all the list data in CPtrArray instead of std::vector. Makes me think if I am already using virtual list without really know the terminology? :D All I know is I am definetely not using owner drawn listview style.
It doesn't really matter which container you use. std::list would work as well as std::vector (and maybe even yield better performance). I say the container doesn't matter because the container items aren't reference by index in my project. I use the Set[Get]ItemData methods to store/retrieve the pointer to the item directly - the index isn't use.
One thing to try for performance is to use a std::list and instead of having the list store a pointer to the item, try it with storing the actual item. You'll need to write a copy constructor for the items but it might be more performant that way.
Re: Eliminate PumpMessage()
Quote:
Originally Posted by
VladimirF
So what is the problem at this moment?
You do not get a visual feedback from the list that it is being populated?
My original issue of feedback was resolved my cutting down on the number of messages I post to main thread. I was just following up on Arjay's demo and clearing some thoughts about virtual lists
Quote:
Originally Posted by
Arjay
It doesn't really matter which container you use. std::list would work as well as std::vector (and maybe even yield better performance). I say the container doesn't matter because the container items aren't reference by index in my project. I use the Set[Get]ItemData methods to store/retrieve the pointer to the item directly - the index isn't use.
One thing to try for performance is to use a std::list and instead of having the list store a pointer to the item, try it with storing the actual item. You'll need to write a copy constructor for the items but it might be more performant that way.
I agree the container doesn't matter as long as it is not CArray object because it could reallocates itself if expanded and the pointers stored in list could become old and invalid. std::list is a link list and is good to go!
Thank you all guys, really appreciated it!