Re: Use a DC in a worker thread
- From: Joseph M. Newcomer <newcomer@xxxxxxxxxxxx>
- Date: Fri, 26 Sep 2008 11:00:17 -0400
See below...
On Thu, 25 Sep 2008 23:14:22 -0700 (PDT), Faisal <faisalm83@xxxxxxxxx> wrote:
On Sep 26, 9:21 am, Joseph M. Newcomer <newco...@xxxxxxxxxxxx> wrote:****
See below...
On Thu, 25 Sep 2008 20:37:07 -0700 (PDT), Faisal <faisal...@xxxxxxxxx> wrote:
On Sep 25, 3:54 pm, Joseph M. Newcomer <newco...@xxxxxxxxxxxx> wrote:
See below....
On Wed, 24 Sep 2008 22:19:41 -0700 (PDT), Faisal <faisal...@xxxxxxxxx> wrote:
On Sep 24, 8:35 pm, Joseph M. Newcomer <newco...@xxxxxxxxxxxx> wrote:
Yes. There is actually an exmaple in the MSDN that does this.
The key here is to make sure that at no time do two threads have DCs to the same window,
and that at no time do two threads try to manipulate the same DC.
You cannot do a progress bar only because StretchBlt does not give you progress
indications. BUT, if you broke the problem into a sequence of StretchBlt calls (say the
image is 1024x1024; if you did StretchBlt of 128x128 pieces, you would have 64
opportunities to update the progress bar, one for each little piece you did. I don't have
any idea how good the output would look because I don't know how sensitive StretchBlt is
to boundary conditions, but on the whole, you can assume that you are going to get crap.
Serious graphics requires doing decent image convolution algorithms that will average
multiple pixels (there's a huge body of literature on this).
However, another solution is to simply StretchBlt into a memory DC and only handle the
resulting BLT to the client area DC in the main thread.
Does bliting to a memory DC and then to the device dc have any
performance advantage?
****
Sounds like you're asking "Is doing something twice faster than doing something once?" The
answer is obvious.
Generally, this technique is used not for performance reasons but to reduce screen
flicker, which can be an important consideration.
****>In my case, I think the application takes the same time.
****
And how did you determine this? If the answer does not include the use of
QueryPerformanceCounter then you don't actually know.
joe
****
An advantage of the piecewise transformation (if it produces an acceptable image) is that
you can actually display pieces at a time so the user doesn't just see a totally blank
screen. Problem is that you can't have a bitmap selected into two different DCs at the
same time, so you would have to be careful that you didn't need to do this to make it
work, whether the rendering was done to a memory DC or directly to the screen.
joe
On Wed, 24 Sep 2008 07:06:26 -0700 (PDT), Faisal <faisal...@xxxxxxxxx> wrote:
My application uses StretchBlt() to print some images and graphs.
Since StretchBlt () takes a few minutes to complete, user feels the
application is hanged.
As a solution I would like to move the StretchBlt () to a separate
thread. Is this a good solution? Can I manipulate a DC from a thread
other than its owner?
Also, I would like to show a progress bar while printing. How would I
know the steps once the StretchBlt()went to hang?
Joseph M. Newcomer [MVP]
email: newco...@xxxxxxxxxxxx
Web:http://www.flounder.com
MVP Tips:http://www.flounder.com/mvp_tips.htm
Joseph M. Newcomer [MVP]
email: newco...@xxxxxxxxxxxx
Web:http://www.flounder.com
MVP Tips:http://www.flounder.com/mvp_tips.htm
I didn't measure by time checking. I'm just giving my observation.
I thought this would increase the performance. So I asked that
question.
****
Any time you think you know one implementation is faster than another, but you have no
basis for this, you have to measure.
****
Now I modified the code to show a progress bar. For this I created a
UI thread before my long operation. In this thread I'm showing a
dialog with progress bar. And in between each small StrechBlt()
operation I tried to update the progress bar.
****
DO NOT create a UI thread that shows a window. Especially NEVER create a UI thread that
creates a dialog. This is just out-and-out a mistake. You are going to end up with
cross-thread SendMessage operations and you will eventually get screwed by this. The rule
is, the one-and-only GUI thread has responsibility for ALL windows, period, and NO OTHER
THREAD EVER has a user-visible window.
Creating a UI thread with a progress dialog is the wrong solution. The only question is
how long it will take you to discover that the solution doesn't work. Post-deployment is
the usual time the potential disasters become real disasters, which is why this is never
done
(A good predicate to test this: only beginners who don't understand threading would adopt
this solution; those of us who understand threading would not even consider the
possibility of creating a secondary thread with a dialog. There's several reasons we
don't do this, but the fact that we don't should be an important message to you)
****
But, to my surprise dialog is not showing up, when i started the new
UI thread. Only when I'm returning from the current function in the
main thread, the dialog is showing. Is this an expected behavior?
While debugging it shows that the thread is starting up, but in the
dialog.create() call hangs and it returns only when the current
command procedure in main thread is returned. The exact function with
hangs is ::CreateDialogIndirect().
****
This would not surprise me in the slightest. Do you understand how interthread messaging
works? Note that because the dialog can't come up until the SendMessage to its parent has
returned, and that can't return if the main thread is busy doing something, so you got the
case where the wrong solution fails immediately.
****
A stripped down version of my code is given below.
void CMainFrame::OnUIthrd()
{
CPBThread* pThread = new CPBThread();
pThread->CreateThread(CREATE_SUSPENDED)
pThread->ResumeThread();
****
WHy are you creating it suspended and resuming it immediately? The only reason to create
it suspended and resume it is so that between these two statements you can do important
things.
****
for( int i = 0; i < 100; i++ )
{
Sleep( 100 ); // In real code I'm doing the stretch blit from here
pThread->m_nStep = i;
}
****
Absolutely, positively, without any doubt whatsoever, this is going to fail completely.
You are doing your computations in the main GUI thread, which blocks the main GUI thread's
message pump, which means the dialog won't come up properly. What part of "do the work in
a secondary thread", that we have been saying all along, did you miss? We don't say that
because it amuses us to do so; we say it because it is the only sane solution.
****
SetEvent( pThread->m_hEventKill );
****
What is the purpose of this pointless SetEvent? It cannot possibly be associated with
killing the dialog, because the dialog cannot possibly be waiting on an event. So it is
immediately obvious just by seeing this line that your fundamental design is flawed beyond
redemption.
****>}
Thread code is given here
BOOL CPBThread::InitInstance()
{
CProgessDlg* pProDlg = new CProgessDlg();
pProDlg->Create(IDD_PROGRSS, 0 );
****
You are creating a modeless dialog, so the correct code would be
pProDlg->Create(CProgressDlg::IDD);
there is no reason to use the explicit dialog ID
****> pProDlg->ShowWindow(SW_SHOW);
// loop but check for kill notification
while (WaitForSingleObject(m_hEventKill, 0) == WAIT_TIMEOUT)
{
pProDlg->m_Progress.SetPos( m_nStep );
}
****
There is nothing construably sensible here. This merely steps the progress bar
infinitely, because a 0 delay times out instantly. This thread pins your CPU utilization
at 100% because it never stops. Another way to tell a fundamentally flawed design is it
has a line that says
while(WaitForSingleObject( ...) == WAIT_TIMEOUT)
since WaitForSingleObject can return several possible values, and testing for just one of
them is not particularly useful.
****
pProDlg->DestroyWindow();
delete pProDlg;
****
ABSOLUTELY WRONG! YOU CANNOT DO THE delete HERE. You MUST do it in the PostNcDestroy
handler!
****> pProDlg = NULL;
****
Why do you care, at this point? You should not ever be able to touch this variable after
the return
****
// avoid entering standard message loop by returning FALSE
return FALSE;
}
I think there's some serious mistake in my understanding of UI thread.
How can I solve this problem?
****
Yes, there is. You must not use UI threads with user-visible windows (the exceptions to
this are very rare, very exotic, and probably are implemented incorrectly almost all the
time). You must not do long computations in the main GUI thread. You must not poll
synchronization objects; if you can't use INFINITE, the design is very suspect. The cases
of using timeout are now to the level where I don't ever use anything other than INFINITE
unless I have a compelling reason to do so. You are using the polling of an event to
substitute for a positive messaging system (which I showed how to write). You have to
eliminate the UI thread as a way to bring up the dialog, bring up the dialog in the main
UI thread, use interthread PostMessage to deal with notifications, and do all the
computation in a secondary thread. Whether you update the display by using a DC in the
primary or secondary thread is debatable, but I'd put all the updates in the main GUI
thread and let the secondary thread simply tell me when it has a computation I should care
about.
You can solve the problem by eliminating most of the code you have written and replacing
with code that more closely resembles what I originally suggested, which removes all these
problems (I typed that code in about as fast as I could type, because this is the
architecture I use for most of my multithreaded code, and I've been ...
read more »
Thanks Joe for correcting me.
The reason for going with a UI thread solution is, the Architect of my
project asked me to develop a framework, in which
1. User can give any dialog pointer and ID to this framework, and it
will create the dialog and show it in a new thread.
The architect is clueless. THis is a bad specification, and will result in erroneous
code. The architect needs to reconsider the specification, and NOT give a specification
that is inconsistent with Best Practice or even Remotely Feasible Practice.
****
2. With some mechanism this dialog can be killed from main thread.****
The main thread can receive an asynchronous notification from the thread and then post it
to the dialog, which will then terminate. But frankly, this is another specification that
indicates the architect is clueless about threading, because this is a bad specification,
and can lead to erroneous code.
****
****
With this implementation she can reuse the implementation in several
places.
You can do this in a variety of ways, including subclassing, virtual methods, or
callbacks. If your architect had a clue about how threading works, this is the kind of
specification you should have been given. But you can't create a correct implementation
for an erroneous specification, no matter how hard you try.
As an alternative, you could package the whole thing into a DLL, and provide a
specification such as
HPROGRESS CMyProgressThread::StartProgressThread(LPMYTHREADPROC proc, LPVOID userdata);
where the definition is
typedef void (CALLBACK * LPMYTHREADPROC)(LPVOID userdata, HPROGRESS hProgressThread);
where you have done, in the header file,
DECLARE_HANDLE(HPROGRESS)
and the code looks like
class CMyProgressManager {
public:
CProgressManager();
virtual ~CProgressManager();
static HPROGRESS Start(
LPMYTHREADPROC proc,
LPVOID userdata,
CWnd * parent = NULL);
static void SetRange(HPROGRESS h, UINT high);
static void SetPos(HPROGRESS h, UINT pos);
static void SetText(HPROGRESS h, LPCTSTR msg);
static void Stop(HPROGRESS h);
static BOOL IsRunning(HPROGRESS h);
static void Close(HPROGRESS h);
protected:
static UINT threadfunc(LPVOID p);
class ProgressHandle {
public:
ProgressHandle() {
dlg = NULL;
proc = NULL;
userdata = NULL;
thread = NULL;
parent = NULL;
running = TRUE;
}
CProgressDlg * dlg;
CWnd * parent;
LPMYTHREADPROC proc;
LPVOID userdata;
CMyProgressThread * thread;
BOOL running;
};
}
/* static */ HPROGRESS CMyProgressManager::Start(
LPMYTHREADPROC proc, LPVOID userdata,
CWnd * parent = NULL)
{
ProgressHandle * h = new ProgressHandle;
h->dlg = new CProgressDlg;
h->userdata = userdata;
h->proc = proc;
h->parent = parent != NULL ? parent : AfxGetMainWnd();
if(!dlg->Create(CProgressDlg::IDD, parent))
{
delete dlg;
h->dlg = NULL;
return (HPROGRESS)NULL;
}
dlg->progresshandle = (HPROGRESS)h;
h->thread = AfxBeginThread(threadfunc, h);
if(h->thread == NULL)
{ /* thread failed */
dlg->DestroyWindow();
return (HPROGRESS)NULL;
} /* thread failed */
return (HPROGRESS)h;
}
you will need an exception class to handle exceptions:
class CThreadAbortedException : public CException {
public:
CThreadAbortedException(DWORD err) { Error = err; }
DWORD Error;
};
The dialog will not actually appear until the caller or the thread post a message to it to
show it.
/* static */ UINT CMyProgressManager::threadfunc(LPVOID p)
{
ProgressHandle * h = (ProgressHandle *)p;
DWORD err = ERROR_SUCCESS;
try {
h->proc(h->userdata, (HPROGRESS)h);
}
catch(CThreadAbortedException * e)
{
err = e->Error;
e->Delete();
}
catch(CMemoryException * e)
{
err = ERROR_NOT_ENOUGH_MEMORY;
e->Delete();
}
catch(CException * e)
{
err = EVENT_E_INTERNALEXCEPTION;
e->Delete();
}
h->running = FALSE;
h->dlg->PostMessage(UWM_THREAD_STOPPED, (WPARAM)h);
return 0;
}
/* static */ void CMyProgressManager::SetRange(HPROGRESS h, UINT high)
{
ProgressHandle * ph = (ProgressHandle *)h;
ph->dlg->PostMessage(UWM_SET_RANGE, (WPARAM)high);
}
/* static */ void CMyProgressManager::SetText(HPROGRESS h, LPCTSTR msg)
{
ProgressHandle * ph = (ProgressHandle *)h;
ph->dlg->PostMessage(UWM_SET_TEXT, (WPARAM)new CString(msg);
}
/* static */ void CMyProgressManager::SetPos(HPROGRESS h, UINT pos)
{
ProgressHandle * ph = (ProgressHandle *)h;
ph->dlg->PostMessage(UWM_SET_POS, (WPARAM)pos);
}
/* static */ void CMyProgressManager::Stop(HPROGRESS h)
{
ProgressHandle * ph = (ProgressHandle *)h;
ph->running = FALSE;
}
/* static */ BOOL CMyProgressManager::IsRunning(HPROGRESS h)
{
ProgressHandle * ph =(ProgressHandle *)h;
return ph->running;
}
/* static */ BOOL CMyProgessManager::Close(HPROGRESS h)
{
ProgressHandle * ph = (ProgressHandle *) h;
ASSERT(!ph->running);
if(ph->running)
return FALSE;
ph->dlg->DestroyWindow();
delete ph;
return TRUE;
}
You can make a decision that until the text is set, the dialog does not display, so the
code can be
LRESULT CMyProgressDlg::OnSetText(WPARAM wParam, LPARAM)
{
CString * msg = (CString *)wParam;
c_Message.SetWindowText(*msg);
delete msg;
ShowWindow(SW_SHOW);
return 0;
}
LRESULT CMyProgressDlg::OnSetRange(WPARAM wParam, LPARAM)
{
c_Progress.SetRange32(0, (int)wParam);
return 0;
}
LRESULT CMyProgressDlg::OnSetPos(WPARAM wParam, LPARAM)
{
c_Progress.SetPos32((int)wParam);
return 0;
}
LRESULT CMyProgessDlg::OnThreadStopped(WPARAM wParam, LPARAM)
{
ProgressHandle * h = (ProgressHandle *)wParam;
h->parent->PostMessage(UWM_THREAD_STOPPED);
return 0;
}
void CMyProgessDlg::PostNcDestroy()
{
CDialog::PostNcDestroy();
delete this;
}
void CMyProgessDlg::OnOK()
{
}
void CMyProgessDlg::OnCancel()
{
}
void CMyProgressDlg::OnBnClickedStop()
{
CMyProgressManager::Stop(progresshandle);
}
In your app you might do
void CMyWindowClass::OnStartThread()
{
HPROGRESS h = CMyProgressManager::StartProgressThread(
mythreadfunc,
this,
this);
if(h == NULL)
{
... report failure
return;
}
CMyProgressManager::SetProgressRange(h, whatever);
CMyProgressManager::SetProgressPos(h, 0);
CString msg;
msg.LoadString(IDS_MY_MESSAGE);
CMyProgressManager::SetProgressText(h, msg);
}
LRESULT CMyWIndowClass::OnThreadTerminated(WPARAM wParam, LPARAM)
{
HPROGRESS h = (HPROGRESS)wParam;
CMyProgressManager::Close(h);
...and deal with your application-specific response to the thread
return 0;
}
Now, in your thread function, remember that it has an HPROGRESS parameter, so you can call
any of the methods at any time.
The window does not close until the thread terminates. You handle this by an internal
PostMessage as indicated.
****
From your comments I understand that showing a dialog from a UI threadis a really bad design. And the problem comes when the UI thread try
to send some message to the parent window. In my sample, I specified
the parent as NULL, so the reason for hang may be some other
sendmessage. Is my understanding is correct? So why really the UI
thread need to send messages to Main thread, even if the parent is
NULL?
In that case, the parent is defined to be the main window of your app, as specified by the
CDialog::Create call (reading the documentation usually helps in understanding what is
going on). Note that there are potentially several SendMessage calls to the parent that
happen during subwindow creation.
Note this is a sketch; I have not provided all the details, and I think this is sufficient
code, but you cannot expect to dash off something as complex as a reusable progress dialog
class with a worker thread in the minute number of lines you wrote.
Fundamentally,the ONLY valid requirement is:
Create a reusable class that can provide a progress dialog with a progress bar, static
text box, and cancel button, along with an interface that allows the worker thread to
query the cancellation state. The interface shall consist of
a means by which a thread and a dialog can be created, said means being
a call which creates the dialog and thread; the invocation of this
means can only be from the main GUI thread and it is erroneous
to call it from a secondary thread
a means by which the thread computation may be specified, said means being
implemented by an application-specified callback function whose
prototype is specified by this reusable interface
a means by which the thread can query to see if the user has requested
termination of the thread, said means being a boolean variable
which can be queried during the thread loop
a means by which the application can explicitly request that the thread stop
this shall consist of the presence of a button on the dialog which
the user can cllick, and a function which the user can call from within
any thread of control, which shall initiate the shutdown of the
computation by caused the aforementioned query of the boolean
variable returning a false value, indicating the thread should stop
a means by which the application can receive notification that the thread
has stopped, and encode the reason the thread has stopped,
said means being a user-defined message to
notify the application the thread has terminated, which shall
encode the reason
a means by which the application can free the resources utilized by the
thread dialog and thread
There are many variations on this specification and several implementations that could be
chosen to realize the specification; I chose one implementation path which uses a worker
thread. For example, my "encode the reason" for thread termination consists of sending a
standard error code via the asynchronous message. You could choose to pass a pointer to a
much more complex structure.
If I had wanted to be able to suspend and resume the thread, or use it in a way that
required queuing requests to it, I might have written a different specification, and
realized it with a different implementation than the one I chose.
A specification which requires creating a UI thread with a dialog is simply a bogus
specification, written by a totally clueless person. It's what I call "programming by
rumor" as in "I read something about threads that are called UI threads, and this must
mean I can put user interface objects in them, and therefore I assume having once heard of
these, I assume I understand all the implications of using them, and can therefore write a
specification that uses them, without any consideration of the nature of reality". You
were given a bad specification, which is why you can't create a valid implementation.
****
****
Also in which cases we need to create UI threads?
If the secondary thread requires a message pump, you would use a UI thread, but the thread
would have no user-visible windows. A thread that does SAPI or winsock certainly qualify,
and a thread for which you wish to use the message queue as an interthread queueing
mechanism qualifies, but for all practical purposes you NEVER create a UI thread that has
user-visible windows in it (there are extremely rare exceptions, perhaps one per
lifetime).
See my essay on UI threads on my MVP Tips site.
joe
****
Joseph M. Newcomer [MVP]
email: newcomer@xxxxxxxxxxxx
Web: http://www.flounder.com
MVP Tips: http://www.flounder.com/mvp_tips.htm
.
- References:
- Use a DC in a worker thread
- From: Faisal
- Re: Use a DC in a worker thread
- From: Joseph M . Newcomer
- Re: Use a DC in a worker thread
- From: Faisal
- Re: Use a DC in a worker thread
- From: Joseph M . Newcomer
- Re: Use a DC in a worker thread
- From: Faisal
- Re: Use a DC in a worker thread
- From: Joseph M . Newcomer
- Re: Use a DC in a worker thread
- From: Faisal
- Use a DC in a worker thread
- Prev by Date: Visual Studio 2008 SP1 Featue Pack CWinAppEx::LoadState()
- Next by Date: Re: Can I Exclude a DLL at runtime.
- Previous by thread: Re: Use a DC in a worker thread
- Next by thread: VS 2005, problems with manifest and unmanged code
- Index(es):
Relevant Pages
|