Add support for thread-specific log targets.
A worker thread can now have its own log target which will be used directly by the log functions instead of buffering log output in the main thread; the GUI thread in the thread sample shows how it works. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@61422 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
@@ -358,6 +358,12 @@ All:
|
||||
- Added wxTempFile::Flush().
|
||||
- Added support for wxLongLong and wxULongLong in wxVariant.
|
||||
- Added wxVector::swap().
|
||||
- Many wxLog improvements:
|
||||
* wxLogXXX() functions are now thread-safe.
|
||||
* Log levels can now be set independently for different log components.
|
||||
* wxLog::DoLogRecord() has access to the location of the log message
|
||||
(file, line and function name) and id of the thread which generated it.
|
||||
* SetThreadActiveTarget() allows to set up thread-specific log targets.
|
||||
|
||||
All (GUI):
|
||||
|
||||
|
@@ -385,24 +385,22 @@ public:
|
||||
// 17 modal dialogs one after another)
|
||||
virtual void Flush();
|
||||
|
||||
// flush the active target if any
|
||||
static void FlushActive()
|
||||
{
|
||||
if ( !ms_suspendCount )
|
||||
{
|
||||
wxLog *log = GetActiveTarget();
|
||||
if ( log )
|
||||
log->Flush();
|
||||
}
|
||||
}
|
||||
// flush the active target if any and also output any pending messages from
|
||||
// background threads
|
||||
static void FlushActive();
|
||||
|
||||
// only one sink is active at each moment
|
||||
// get current log target, will call wxApp::CreateLogTarget() to
|
||||
// create one if none exists
|
||||
// only one sink is active at each moment get current log target, will call
|
||||
// wxAppTraits::CreateLogTarget() to create one if none exists
|
||||
static wxLog *GetActiveTarget();
|
||||
|
||||
// change log target, pLogger may be NULL
|
||||
static wxLog *SetActiveTarget(wxLog *pLogger);
|
||||
// change log target, logger may be NULL
|
||||
static wxLog *SetActiveTarget(wxLog *logger);
|
||||
|
||||
#if wxUSE_THREADS
|
||||
// change log target for the current thread only, shouldn't be called from
|
||||
// the main thread as it doesn't use thread-specific log target
|
||||
static wxLog *SetThreadActiveTarget(wxLog *logger);
|
||||
#endif // wxUSE_THREADS
|
||||
|
||||
// suspend the message flushing of the main target until the next call
|
||||
// to Resume() - this is mainly for internal use (to prevent wxYield()
|
||||
@@ -580,9 +578,16 @@ protected:
|
||||
unsigned LogLastRepeatIfNeeded();
|
||||
|
||||
private:
|
||||
// called from OnLog() if it's called from the main thread and from Flush()
|
||||
#if wxUSE_THREADS
|
||||
// called from FlushActive() to really log any buffered messages logged
|
||||
// from the other threads
|
||||
void FlushThreadMessages();
|
||||
#endif // wxUSE_THREADS
|
||||
|
||||
// called from OnLog() if it's called from the main thread or if we have a
|
||||
// (presumably MT-safe) thread-specific logger and by FlushThreadMessages()
|
||||
// when it plays back the buffered messages logged from the other threads
|
||||
void OnLogInMainThread(wxLogLevel level,
|
||||
void CallDoLogNow(wxLogLevel level,
|
||||
const wxString& msg,
|
||||
const wxLogRecordInfo& info);
|
||||
|
||||
|
43
include/wx/private/threadinfo.h
Normal file
43
include/wx/private/threadinfo.h
Normal file
@@ -0,0 +1,43 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Name: wx/private/threadinfo.h
|
||||
// Purpose: declaration of wxThreadSpecificInfo: thread-specific information
|
||||
// Author: Vadim Zeitlin
|
||||
// Created: 2009-07-13
|
||||
// RCS-ID: $Id: wxhead.h,v 1.11 2009-06-29 10:23:04 zeitlin Exp $
|
||||
// Copyright: (c) 2009 Vadim Zeitlin <vadim@wxwidgets.org>
|
||||
// Licence: wxWindows licence
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef _WX_PRIVATE_THREADINFO_H_
|
||||
#define _WX_PRIVATE_THREADINFO_H_
|
||||
|
||||
#if wxUSE_THREADS
|
||||
|
||||
#include "wx/tls.h"
|
||||
|
||||
class WXDLLIMPEXP_FWD_BASE wxLog;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// wxThreadSpecificInfo: contains all thread-specific information used by wx
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// currently the only thread-specific information we use is the active wxLog
|
||||
// target but more could be added in the future (e.g. current wxLocale would be
|
||||
// a likely candidate) and we will group all of them in this struct to avoid
|
||||
// consuming more TLS slots than necessary as there is only a limited number of
|
||||
// them
|
||||
|
||||
// NB: this must be a POD to be stored in TLS
|
||||
struct wxThreadSpecificInfo
|
||||
{
|
||||
wxLog *logger;
|
||||
};
|
||||
|
||||
// currently this is defined in src/common/log.cpp
|
||||
extern wxTLS_TYPE(wxThreadSpecificInfo) wxThreadInfoVar;
|
||||
#define wxThreadInfo wxTLS_VALUE(wxThreadInfoVar)
|
||||
|
||||
#endif // wxUSE_THREADS
|
||||
|
||||
#endif // _WX_PRIVATE_THREADINFO_H_
|
||||
|
@@ -759,24 +759,36 @@ public:
|
||||
|
||||
If the buffer is already empty, nothing happens.
|
||||
|
||||
It should only be called from the main application thread.
|
||||
|
||||
If you override this method in a derived class, call the base class
|
||||
version first, before doing anything else, to ensure that any buffered
|
||||
messages from the other threads are logged.
|
||||
version first, before doing anything else.
|
||||
*/
|
||||
virtual void Flush();
|
||||
|
||||
/**
|
||||
Flushes the current log target if any, does nothing if there is none.
|
||||
|
||||
As Flush() itself, this method should only be called from the main
|
||||
application thread.
|
||||
When this method is called from the main thread context, it also
|
||||
flushes any previously buffered messages logged by the other threads.
|
||||
When it is called from the other threads it simply calls Flush() on the
|
||||
currently active log target, so it mostly makes sense to do this if a
|
||||
thread has its own logger set with SetThreadActiveTarget().
|
||||
*/
|
||||
static void FlushActive();
|
||||
|
||||
/**
|
||||
Returns the pointer to the active log target (may be @NULL).
|
||||
|
||||
Notice that if SetActiveTarget() hadn't been previously explicitly
|
||||
called, this function will by default try to create a log target by
|
||||
calling wxAppTraits::CreateLogTarget() which may be overridden in a
|
||||
user-defined traits class to change the default behaviour. You may also
|
||||
call DontCreateOnDemand() to disable this behaviour.
|
||||
|
||||
When this function is called from threads other than main one,
|
||||
auto-creation doesn't happen. But if the thread has a thread-specific
|
||||
log target previously set by SetThreadActiveTarget(), it is returned
|
||||
instead of the global one. Otherwise, the global log target is
|
||||
returned.
|
||||
*/
|
||||
static wxLog* GetActiveTarget();
|
||||
|
||||
@@ -866,6 +878,8 @@ public:
|
||||
To suppress logging use a new instance of wxLogNull not @NULL. If the
|
||||
active log target is set to @NULL a new default log target will be
|
||||
created when logging occurs.
|
||||
|
||||
@see SetThreadActiveTarget()
|
||||
*/
|
||||
static wxLog* SetActiveTarget(wxLog* logtarget);
|
||||
|
||||
@@ -906,6 +920,32 @@ public:
|
||||
*/
|
||||
static void SetRepetitionCounting(bool repetCounting = true);
|
||||
|
||||
/**
|
||||
Sets a thread-specific log target.
|
||||
|
||||
The log target passed to this function will be used for all messages
|
||||
logged by the current thread using the usual wxLog functions. This
|
||||
shouldn't be called from the main thread which never uses a thread-
|
||||
specific log target but can be used for the other threads to handle
|
||||
thread logging completely separately; instead of buffering thread log
|
||||
messages in the main thread logger.
|
||||
|
||||
Notice that unlike for SetActiveTarget(), wxWidgets does not destroy
|
||||
the thread-specific log targets when the thread terminates so doing
|
||||
this is your responsibility.
|
||||
|
||||
This method is only available if @c wxUSE_THREADS is 1, i.e. wxWidgets
|
||||
was compiled with threads support.
|
||||
|
||||
@param logger
|
||||
The new thread-specific log target, possibly @NULL.
|
||||
@return
|
||||
The previous thread-specific log target, initially @NULL.
|
||||
|
||||
@since 2.9.1
|
||||
*/
|
||||
static wxLog *SetThreadActiveTarget(wxLog *logger);
|
||||
|
||||
/**
|
||||
Sets the timestamp format prepended by the default log targets to all
|
||||
messages. The string may contain any normal characters as well as %
|
||||
|
@@ -5,7 +5,7 @@
|
||||
// Modified by:
|
||||
// Created: 06/16/98
|
||||
// RCS-ID: $Id$
|
||||
// Copyright: (c) 1998-2002 wxWidgets team
|
||||
// Copyright: (c) 1998-2009 wxWidgets team
|
||||
// Licence: wxWindows license
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -720,8 +720,8 @@ void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event) )
|
||||
wxMessageDialog dialog(this,
|
||||
_T("wxWidgets multithreaded application sample\n")
|
||||
_T("(c) 1998 Julian Smart, Guilhem Lavaux\n")
|
||||
_T("(c) 1999 Vadim Zeitlin\n")
|
||||
_T("(c) 2000 Robert Roebling"),
|
||||
_T("(c) 2000 Robert Roebling\n")
|
||||
_T("(c) 1999,2009 Vadim Zeitlin"),
|
||||
_T("About wxThread sample"),
|
||||
wxOK | wxICON_INFORMATION);
|
||||
|
||||
@@ -1003,6 +1003,14 @@ wxThread::ExitCode MyWorkerThread::Entry()
|
||||
|
||||
wxThread::ExitCode MyGUIThread::Entry()
|
||||
{
|
||||
// this goes to the main window
|
||||
wxLogMessage("GUI thread starting");
|
||||
|
||||
// use a thread-specific log target for this thread to show that its
|
||||
// messages don't appear in the main window while it runs
|
||||
wxLogBuffer logBuf;
|
||||
wxLog::SetThreadActiveTarget(&logBuf);
|
||||
|
||||
for (int i=0; i<GUITHREAD_NUM_UPDATES && !TestDestroy(); i++)
|
||||
{
|
||||
// inform the GUI toolkit that we're going to use GUI functions
|
||||
@@ -1029,10 +1037,22 @@ wxThread::ExitCode MyGUIThread::Entry()
|
||||
event.SetInt(i+1);
|
||||
wxQueueEvent( m_dlg, event.Clone() );
|
||||
|
||||
if ( !((i + 1) % 10) )
|
||||
{
|
||||
// this message will go to the buffer
|
||||
wxLogMessage("Step #%d.", i + 1);
|
||||
}
|
||||
|
||||
// give the main thread the time to refresh before we lock the GUI mutex again
|
||||
// FIXME: find a better way to do this!
|
||||
wxMilliSleep(100);
|
||||
}
|
||||
|
||||
// now remove the thread-specific thread target
|
||||
wxLog::SetThreadActiveTarget(NULL);
|
||||
|
||||
// so that this goes to the main window again
|
||||
wxLogMessage("GUI thread finished.");
|
||||
|
||||
return (ExitCode)0;
|
||||
}
|
||||
|
@@ -42,6 +42,7 @@
|
||||
#include "wx/msgout.h"
|
||||
#include "wx/textfile.h"
|
||||
#include "wx/thread.h"
|
||||
#include "wx/private/threadinfo.h"
|
||||
#include "wx/crt.h"
|
||||
#include "wx/vector.h"
|
||||
|
||||
@@ -69,6 +70,8 @@ const char *wxLOG_COMPONENT = "";
|
||||
|
||||
#if wxUSE_THREADS
|
||||
|
||||
wxTLS_TYPE(wxThreadSpecificInfo) wxThreadInfoVar;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
@@ -259,29 +262,46 @@ wxLog::OnLog(wxLogLevel level,
|
||||
#endif
|
||||
}
|
||||
|
||||
wxLog *pLogger = GetActiveTarget();
|
||||
if ( !pLogger )
|
||||
return;
|
||||
wxLog *logger;
|
||||
|
||||
#if wxUSE_THREADS
|
||||
if ( !wxThread::IsMain() )
|
||||
{
|
||||
logger = wxThreadInfo.logger;
|
||||
if ( !logger )
|
||||
{
|
||||
if ( ms_pLogger )
|
||||
{
|
||||
// buffer the messages until they can be shown from the main
|
||||
// thread
|
||||
wxCriticalSectionLocker lock(GetBackgroundLogCS());
|
||||
|
||||
gs_bufferedLogRecords.push_back(wxLogRecord(level, msg, info));
|
||||
|
||||
// ensure that our Flush() will be called soon
|
||||
wxWakeUpIdle();
|
||||
}
|
||||
//else: we don't have any logger at all, there is no need to log
|
||||
// anything
|
||||
|
||||
return;
|
||||
}
|
||||
//else: we have a thread-specific logger, we can send messages to it
|
||||
// directly
|
||||
}
|
||||
else
|
||||
#endif // wxUSE_THREADS
|
||||
{
|
||||
logger = ms_pLogger;
|
||||
if ( !logger )
|
||||
return;
|
||||
}
|
||||
|
||||
pLogger->OnLogInMainThread(level, msg, info);
|
||||
logger->CallDoLogNow(level, msg, info);
|
||||
}
|
||||
|
||||
void
|
||||
wxLog::OnLogInMainThread(wxLogLevel level,
|
||||
wxLog::CallDoLogNow(wxLogLevel level,
|
||||
const wxString& msg,
|
||||
const wxLogRecordInfo& info)
|
||||
{
|
||||
@@ -429,6 +449,19 @@ void wxLog::DoLog(wxLogLevel WXUNUSED(level), const wchar_t *wzString, time_t t)
|
||||
|
||||
wxLog *wxLog::GetActiveTarget()
|
||||
{
|
||||
#if wxUSE_THREADS
|
||||
if ( !wxThread::IsMain() )
|
||||
{
|
||||
// check if we have a thread-specific log target
|
||||
wxLog * const logger = wxThreadInfo.logger;
|
||||
|
||||
// the code below should be only executed for the main thread as
|
||||
// CreateLogTarget() is not meant for auto-creating log targets for
|
||||
// worker threads so skip it in any case
|
||||
return logger ? logger : ms_pLogger;
|
||||
}
|
||||
#endif // wxUSE_THREADS
|
||||
|
||||
if ( ms_bAutoCreate && ms_pLogger == NULL ) {
|
||||
// prevent infinite recursion if someone calls wxLogXXX() from
|
||||
// wxApp::CreateLogTarget()
|
||||
@@ -465,6 +498,22 @@ wxLog *wxLog::SetActiveTarget(wxLog *pLogger)
|
||||
return pOldLogger;
|
||||
}
|
||||
|
||||
#if wxUSE_THREADS
|
||||
/* static */
|
||||
wxLog *wxLog::SetThreadActiveTarget(wxLog *logger)
|
||||
{
|
||||
wxASSERT_MSG( !wxThread::IsMain(), "use SetActiveTarget() for main thread" );
|
||||
|
||||
wxLog * const oldLogger = wxThreadInfo.logger;
|
||||
if ( oldLogger )
|
||||
oldLogger->Flush();
|
||||
|
||||
wxThreadInfo.logger = logger;
|
||||
|
||||
return oldLogger;
|
||||
}
|
||||
#endif // wxUSE_THREADS
|
||||
|
||||
void wxLog::DontCreateOnDemand()
|
||||
{
|
||||
ms_bAutoCreate = false;
|
||||
@@ -582,12 +631,10 @@ void wxLog::TimeStamp(wxString *str)
|
||||
#endif // wxUSE_DATETIME
|
||||
}
|
||||
|
||||
void wxLog::Flush()
|
||||
{
|
||||
#if wxUSE_THREADS
|
||||
wxASSERT_MSG( wxThread::IsMain(),
|
||||
"should be called from the main thread only" );
|
||||
|
||||
void wxLog::FlushThreadMessages()
|
||||
{
|
||||
// check if we have queued messages from other threads
|
||||
wxLogRecords bufferedLogRecords;
|
||||
|
||||
@@ -605,14 +652,36 @@ void wxLog::Flush()
|
||||
it != bufferedLogRecords.end();
|
||||
++it )
|
||||
{
|
||||
OnLogInMainThread(it->level, it->msg, it->info);
|
||||
CallDoLogNow(it->level, it->msg, it->info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // wxUSE_THREADS
|
||||
|
||||
void wxLog::Flush()
|
||||
{
|
||||
LogLastRepeatIfNeeded();
|
||||
}
|
||||
|
||||
/* static */
|
||||
void wxLog::FlushActive()
|
||||
{
|
||||
if ( ms_suspendCount )
|
||||
return;
|
||||
|
||||
wxLog * const log = GetActiveTarget();
|
||||
if ( log )
|
||||
{
|
||||
#if wxUSE_THREADS
|
||||
if ( wxThread::IsMain() )
|
||||
log->FlushThreadMessages();
|
||||
#endif // wxUSE_THREADS
|
||||
|
||||
log->Flush();
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// wxLogBuffer implementation
|
||||
// ----------------------------------------------------------------------------
|
||||
|
Reference in New Issue
Block a user