Merge branch 'msw-wakeup-no-msg'

Restore using event objects, instead of posting WM_NULL messages, for
idle wakeup under MSW.
This commit is contained in:
Vadim Zeitlin
2018-01-14 13:38:04 +01:00
10 changed files with 123 additions and 125 deletions

View File

@@ -128,8 +128,9 @@ public:
// idle handling
// -------------
// make sure that idle events are sent again
virtual void WakeUpIdle();
// make sure that idle events are sent again: this is just an obsolete
// synonym for WakeUp()
void WakeUpIdle() { WakeUp(); }
// this virtual function is called when the application
// becomes idle and by default it forwards to wxApp::ProcessIdle() and

View File

@@ -103,6 +103,10 @@ public:
// use it.
static wxLayoutDirection MSWGetDefaultLayout(wxWindow* parent = NULL);
// Call ProcessPendingEvents() but only if we need to do it, i.e. there was
// a recent call to WakeUpIdle().
void MSWProcessPendingEventsIfNeeded();
protected:
int m_printMode; // wxPRINT_WINDOWS, wxPRINT_POSTSCRIPT

View File

@@ -53,7 +53,6 @@ public:
// override/implement base class virtuals
virtual bool Dispatch() wxOVERRIDE;
virtual int DispatchTimeout(unsigned long timeout) wxOVERRIDE;
virtual void WakeUp() wxOVERRIDE;
protected:
virtual void OnNextIteration() wxOVERRIDE;

View File

@@ -15,9 +15,24 @@ class WXDLLIMPEXP_BASE wxMSWEventLoopBase : public wxEventLoopManual
{
public:
wxMSWEventLoopBase();
virtual ~wxMSWEventLoopBase();
// implement base class pure virtuals
virtual bool Pending() const wxOVERRIDE;
virtual void WakeUp() wxOVERRIDE;
#if wxUSE_THREADS
// MSW-specific method to wait for the termination of the specified (by its
// native handle) thread or any input message arriving (in GUI case).
//
// Return value is WAIT_OBJECT_0 if the thread terminated, WAIT_OBJECT_0+1
// if a message arrived with anything else indicating an error.
WXDWORD MSWWaitForThread(WXHANDLE hThread);
#endif // wxUSE_THREADS
// Return true if wake up was requested and not handled yet, i.e. if
// m_heventWake is signaled.
bool MSWIsWakeUpRequested();
protected:
// get the next message from queue and return true or return false if we
@@ -25,8 +40,13 @@ protected:
bool GetNextMessage(WXMSG *msg);
// same as above but with a timeout and return value can be -1 meaning that
// time out expired in addition to
// time out expired in addition to true/false
int GetNextMessageTimeout(WXMSG *msg, unsigned long timeout);
private:
// An auto-reset Win32 event which is signalled when we need to wake up the
// main thread waiting in GetNextMessage[Timeout]().
WXHANDLE m_heventWake;
};
#if wxUSE_CONSOLE_EVENTLOOP
@@ -39,7 +59,6 @@ public:
// override/implement base class virtuals
virtual bool Dispatch() wxOVERRIDE;
virtual int DispatchTimeout(unsigned long timeout) wxOVERRIDE;
virtual void WakeUp() wxOVERRIDE;
// Windows-specific function to process a single message
virtual void ProcessMessage(WXMSG *msg);

View File

@@ -88,11 +88,6 @@ void wxEventLoopBase::OnExit()
wxTheApp->OnEventLoopExit(this);
}
void wxEventLoopBase::WakeUpIdle()
{
WakeUp();
}
bool wxEventLoopBase::ProcessIdle()
{
return wxTheApp && wxTheApp->ProcessIdle();

View File

@@ -248,23 +248,16 @@ WXDWORD wxGUIAppTraits::WaitForThread(WXHANDLE hThread, int flags)
// have a running event loop as we would never remove them from the message
// queue then and so we would enter an infinite loop as
// MsgWaitForMultipleObjects() keeps returning WAIT_OBJECT_0 + 1.
if ( flags == wxTHREAD_WAIT_BLOCK ||
!wxIsMainThread() ||
!wxEventLoop::GetActive() )
if ( flags == wxTHREAD_WAIT_YIELD && wxIsMainThread() )
{
// Simple blocking wait.
return DoSimpleWaitForThread(hThread);
wxMSWEventLoopBase* const
evtLoop = static_cast<wxMSWEventLoopBase *>(wxEventLoop::GetActive());
if ( evtLoop )
return evtLoop->MSWWaitForThread(hThread);
}
return ::MsgWaitForMultipleObjects
(
1, // number of objects to wait for
(HANDLE *)&hThread, // the objects
false, // wait for any objects, not all
INFINITE, // no timeout
QS_ALLINPUT | // return as soon as there are any events
QS_ALLPOSTMESSAGE
);
// Simple blocking wait.
return DoSimpleWaitForThread(hThread);
}
#endif // wxUSE_THREADS
@@ -785,42 +778,26 @@ void wxApp::OnIdle(wxIdleEvent& WXUNUSED(event))
void wxApp::WakeUpIdle()
{
// Send the top window a dummy message so idle handler processing will
// start up again. Doing it this way ensures that the idle handler
// wakes up in the right thread (see also wxWakeUpMainThread() which does
// the same for the main app thread only)
wxWindow * const topWindow = wxTheApp->GetTopWindow();
if ( topWindow )
wxEventLoopBase * const evtLoop = wxEventLoop::GetActive();
if ( !evtLoop )
{
HWND hwndTop = GetHwndOf(topWindow);
// Do not post WM_NULL if there's already a pending WM_NULL to avoid
// overflowing the message queue.
//
// Notice that due to a limitation of PeekMessage() API (which handles
// 0,0 range specially), we have to check the range from 0-1 instead.
// This still makes it possible to overflow the queue with WM_NULLs by
// interspersing the calles to WakeUpIdle() with windows creation but
// it should be rather hard to do it accidentally.
MSG msg;
if ( !::PeekMessage(&msg, hwndTop, 0, 1, PM_NOREMOVE) ||
::PeekMessage(&msg, hwndTop, 1, 1, PM_NOREMOVE) )
{
// If this fails too, there is really not much we can do, but then
// neither do we need to, as it normally indicates that the window
// queue is full to the brim with the messages and so the main loop
// is running and doesn't need to be woken up.
//
// Notice that we especially should not try use wxLogLastError()
// here as this would lead to another call to wxWakeUpIdle() from
// inside wxLog and stack overflow due to the resulting recursion.
::PostMessage(hwndTop, WM_NULL, 0, 0);
}
// We can't wake up the event loop if there is none and there is just
// no need to do anything in this case, any pending events will be
// handled when the event loop starts.
return;
}
#if wxUSE_THREADS
else
wxWakeUpMainThread();
#endif // wxUSE_THREADS
evtLoop->WakeUp();
}
void wxApp::MSWProcessPendingEventsIfNeeded()
{
// The cast below is safe as wxEventLoop derives from wxMSWEventLoopBase in
// both console and GUI applications.
wxMSWEventLoopBase * const evtLoop
= static_cast<wxMSWEventLoopBase *>(wxEventLoop::GetActive());
if ( evtLoop && evtLoop->MSWIsWakeUpRequested() )
ProcessPendingEvents();
}
// ----------------------------------------------------------------------------

View File

@@ -248,11 +248,6 @@ void wxGUIEventLoop::OnNextIteration()
#endif // wxUSE_THREADS
}
void wxGUIEventLoop::WakeUp()
{
::PostMessage(NULL, WM_NULL, 0, 0);
}
// ----------------------------------------------------------------------------
// Yield to incoming messages

View File

@@ -42,6 +42,17 @@ wxMSWEventLoopBase::wxMSWEventLoopBase()
{
m_shouldExit = false;
m_exitcode = 0;
// Create initially not signalled auto-reset event object.
m_heventWake = ::CreateEvent(NULL, FALSE, FALSE, NULL);
if ( !m_heventWake )
wxLogLastError(wxS("CreateEvent(wake)"));
}
wxMSWEventLoopBase::~wxMSWEventLoopBase()
{
if ( m_heventWake && !::CloseHandle(m_heventWake) )
wxLogLastError(wxS("CloseHandle(wake)"));
}
// ----------------------------------------------------------------------------
@@ -54,26 +65,41 @@ bool wxMSWEventLoopBase::Pending() const
return ::PeekMessage(&msg, 0, 0, 0, PM_NOREMOVE) != 0;
}
void wxMSWEventLoopBase::WakeUp()
{
if ( !::SetEvent(m_heventWake) )
wxLogLastError(wxS("SetEvent(wake)"));
}
bool wxMSWEventLoopBase::MSWIsWakeUpRequested()
{
return ::WaitForSingleObject(m_heventWake, 0) == WAIT_OBJECT_0;
}
#if wxUSE_THREADS
WXDWORD wxMSWEventLoopBase::MSWWaitForThread(WXHANDLE hThread)
{
// The order is important here, the code using this function assumes that
// WAIT_OBJECT_0 indicates the thread termination and anything else -- the
// availability of an input event. So the thread handle must come first.
HANDLE handles[2] = { hThread, m_heventWake };
return ::MsgWaitForMultipleObjects
(
WXSIZEOF(handles), // number of objects to wait for
handles, // the objects
false, // wait for any objects, not all
INFINITE, // no timeout
QS_ALLINPUT | // return as soon as there are any events
QS_ALLPOSTMESSAGE
);
}
#endif // wxUSE_THREADS
bool wxMSWEventLoopBase::GetNextMessage(WXMSG* msg)
{
const BOOL rc = ::GetMessage(msg, NULL, 0, 0);
if ( rc == 0 )
{
// got WM_QUIT
return false;
}
if ( rc == -1 )
{
// should never happen, but let's test for it nevertheless
wxLogLastError(wxT("GetMessage"));
// still break from the loop
return false;
}
return true;
return GetNextMessageTimeout(msg, INFINITE) == TRUE;
}
int wxMSWEventLoopBase::GetNextMessageTimeout(WXMSG *msg, unsigned long timeout)
@@ -81,16 +107,14 @@ int wxMSWEventLoopBase::GetNextMessageTimeout(WXMSG *msg, unsigned long timeout)
// MsgWaitForMultipleObjects() won't notice any input which was already
// examined (e.g. using PeekMessage()) but not yet removed from the queue
// so we need to remove any immediately messages manually
if ( !::PeekMessage(msg, 0, 0, 0, PM_REMOVE) )
while ( !::PeekMessage(msg, 0, 0, 0, PM_REMOVE) )
{
// we use this function just in order to not block longer than the
// given timeout, so we don't pass any handles to it at all
DWORD rc = ::MsgWaitForMultipleObjects
(
0, NULL,
1, &m_heventWake,
FALSE,
timeout,
QS_ALLINPUT
QS_ALLINPUT | QS_ALLPOSTMESSAGE
);
switch ( rc )
@@ -104,13 +128,17 @@ int wxMSWEventLoopBase::GetNextMessageTimeout(WXMSG *msg, unsigned long timeout)
return -1;
case WAIT_OBJECT_0:
if ( !::PeekMessage(msg, 0, 0, 0, PM_REMOVE) )
{
// somehow it may happen that MsgWaitForMultipleObjects()
// returns true but there are no messages -- just treat it
// the same as timeout then
return -1;
}
// We were woken up by a background thread, which means there
// is no actual input message available, but we should still
// return to the event loop, so pretend there was WM_NULL in
// the queue.
wxZeroMemory(*msg);
return TRUE;
case WAIT_OBJECT_0 + 1:
// Some message is supposed to be available, but spurious
// wake ups are also possible, so just return to the loop:
// either we'll get the message or start waiting again.
break;
}
}
@@ -124,13 +152,6 @@ int wxMSWEventLoopBase::GetNextMessageTimeout(WXMSG *msg, unsigned long timeout)
#if wxUSE_CONSOLE_EVENTLOOP
void wxConsoleEventLoop::WakeUp()
{
#if wxUSE_THREADS
wxWakeUpMainThread();
#endif
}
void wxConsoleEventLoop::ProcessMessage(WXMSG *msg)
{
::DispatchMessage(msg);

View File

@@ -1133,18 +1133,6 @@ wxProgressDialogTaskRunner::TaskDialogCallbackProc
LONG_PTR dwRefData
)
{
if ( uNotification == TDN_CREATED )
{
// The main thread may be sitting in an event dispatching loop waiting
// for this dialog to be shown, so make sure it does wake up now that
// it is. Notice that we must do it from here and not from inside the
// block below in which sharedData is locked as otherwise we could
// deadlock if wxWakeUpIdle() dispatched some event which tried to call
// any of wxProgressDialog methods, which also lock this data, from the
// main thread.
wxWakeUpIdle();
}
bool endDialog = false;
// Block for shared data critical section.
@@ -1160,6 +1148,10 @@ wxProgressDialogTaskRunner::TaskDialogCallbackProc
// Store the HWND for the main thread use.
sharedData->m_hwnd = hwnd;
// The main thread is sitting in an event dispatching loop waiting
// for this dialog to be shown, so make sure it does get an event.
wxWakeUpIdle();
// Set the maximum value and disable Close button.
::SendMessage( hwnd,
TDM_SET_PROGRESS_BAR_RANGE,

View File

@@ -7511,10 +7511,9 @@ bool wxWindowMSW::HandleHotKey(WXWPARAM wParam, WXLPARAM lParam)
#endif // wxUSE_HOTKEY
// this class installs a message hook which really wakes up our idle processing
// each time a WM_NULL is received (wxWakeUpIdle does this), even if we're
// sitting inside a local modal loop (e.g. a menu is opened or scrollbar is
// being dragged or even inside ::MessageBox()) and so don't control message
// dispatching otherwise
// each time a message is handled, even if we're sitting inside a local modal
// loop (e.g. a menu is opened or scrollbar is being dragged or even inside
// ::MessageBox()) and so don't control message dispatching otherwise
class wxIdleWakeUpModule : public wxModule
{
public:
@@ -7545,15 +7544,11 @@ public:
static LRESULT CALLBACK MsgHookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
MSG *msg = (MSG*)lParam;
// only process the message if it is actually going to be removed from
// the message queue, this prevents that the same event from being
// processed multiple times if now someone just called PeekMessage()
if ( msg->message == WM_NULL && wParam == PM_REMOVE )
{
wxTheApp->ProcessPendingEvents();
}
// Don't process idle events unless the message is going to be really
// handled, i.e. removed from the queue, as it seems wrong to do it
// just because someone called PeekMessage(PM_NOREMOVE).
if ( wParam == PM_REMOVE )
wxTheApp->MSWProcessPendingEventsIfNeeded();
return CallNextHookEx(ms_hMsgHookProc, nCode, wParam, lParam);
}