Made wxLogXXX() functions thread-safe.

They can now be called from any thread and will buffer the messages until the
current log target is flushed from the main thread. This makes earlier code to
do the same thing specifically for wxLogWindow unnecessary and also allows to
use wxLogMessage() in the thread sample instead of using manual logging there.


git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@61416 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Vadim Zeitlin 2009-07-12 17:02:30 +00:00
parent dbe0872fc8
commit 232addd1cd
6 changed files with 228 additions and 136 deletions

View File

@ -258,5 +258,20 @@ automatically switches to using wxLogStderr if it isn't.
The dialog sample illustrates this approach by defining a custom log target The dialog sample illustrates this approach by defining a custom log target
customizing the dialog used by wxLogGui for the single messages. customizing the dialog used by wxLogGui for the single messages.
@section overview_log_mt Logging in Multi-Threaded Applications
Starting with wxWidgets 2.9.1, logging functions can be safely called from any
thread. Messages logged from threads other than the main one will be buffered
until wxLog::Flush() is called in the main thread (which usually happens during
idle time, i.e. after processing all pending events) and will be really output
only then. Notice that the default GUI logger already only output the messages
when it is flushed, so by default messages from the other threads will be shown
more or less at the same moment as usual. However if you define a custom log
target, messages may be logged out of order, e.g. messages from the main thread
with later timestamp may appear before messages with earlier timestamp logged
from other threads. wxLog does however guarantee that messages logged by each
thread will appear in order in which they were logged.
*/ */

View File

@ -289,6 +289,26 @@ private:
#define wxLOG_KEY_TRACE_MASK "wx.trace_mask" #define wxLOG_KEY_TRACE_MASK "wx.trace_mask"
// ----------------------------------------------------------------------------
// log record: a unit of log output
// ----------------------------------------------------------------------------
struct wxLogRecord
{
wxLogRecord(wxLogLevel level_,
const wxString& msg_,
const wxLogRecordInfo& info_)
: level(level_),
msg(msg_),
info(info_)
{
}
wxLogLevel level;
wxString msg;
wxLogRecordInfo info;
};
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// derive from this class to redirect (or suppress, or ...) log messages // derive from this class to redirect (or suppress, or ...) log messages
// normally, only a single instance of this class exists but it's not enforced // normally, only a single instance of this class exists but it's not enforced
@ -564,6 +584,13 @@ private:
// caller had already locked GetPreviousLogCS() // caller had already locked GetPreviousLogCS()
unsigned LogLastRepeatIfNeededUnlocked(); unsigned LogLastRepeatIfNeededUnlocked();
// called from OnLog() if it's called from the main thread and from Flush()
// when it plays back the buffered messages logged from the other threads
void OnLogInMainThread(wxLogLevel level,
const wxString& msg,
const wxLogRecordInfo& info);
// static variables // static variables
// ---------------- // ----------------

View File

@ -756,14 +756,22 @@ public:
/** /**
Shows all the messages currently in buffer and clears it. Shows all the messages currently in buffer and clears it.
If the buffer is already empty, nothing happens. 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.
*/ */
virtual void Flush(); virtual void Flush();
/** /**
Flushes the current log target if any, does nothing if there is none. Flushes the current log target if any, does nothing if there is none.
@see Flush() As Flush() itself, this method should only be called from the main
application thread.
*/ */
static void FlushActive(); static void FlushActive();

View File

@ -89,28 +89,22 @@ public:
// the main application frame // the main application frame
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
class MyFrame: public wxFrame class MyFrame : public wxFrame,
private wxLog
{ {
public: public:
// ctor // ctor
MyFrame(wxFrame *frame, const wxString& title, int x, int y, int w, int h); MyFrame(const wxString& title);
virtual ~MyFrame(); virtual ~MyFrame();
// this function is MT-safe, i.e. it can be called from worker threads
// safely without any additional locking
void LogThreadMessage(const wxString& text)
{
wxCriticalSectionLocker lock(m_csMessages);
m_messages.push_back(text);
// as we effectively log the messages from the idle event handler,
// ensure it's going to be called now that we have some messages to log
wxWakeUpIdle();
}
// accessors for MyWorkerThread (called in its context!) // accessors for MyWorkerThread (called in its context!)
bool Cancelled(); bool Cancelled();
protected:
virtual void DoLogRecord(wxLogLevel level,
const wxString& msg,
const wxLogRecordInfo& info);
private: private:
// event handlers // event handlers
// -------------- // --------------
@ -136,6 +130,13 @@ private:
void OnUpdateWorker(wxUpdateUIEvent& event); void OnUpdateWorker(wxUpdateUIEvent& event);
// logging helper
void DoLogLine(wxTextCtrl *text,
const wxString& timestr,
const wxString& threadstr,
const wxString& msg);
// thread helper functions // thread helper functions
// ----------------------- // -----------------------
@ -145,9 +146,6 @@ private:
// update display in our status bar: called during idle handling // update display in our status bar: called during idle handling
void UpdateThreadStatus(); void UpdateThreadStatus();
// log the messages queued by LogThreadMessage()
void DoLogThreadMessages();
// internal variables // internal variables
// ------------------ // ------------------
@ -155,6 +153,10 @@ private:
// just some place to put our messages in // just some place to put our messages in
wxTextCtrl *m_txtctrl; wxTextCtrl *m_txtctrl;
// old log target, we replace it with one using m_txtctrl during this
// frame life time
wxLog *m_oldLogger;
// the array of pending messages to be displayed and the critical section // the array of pending messages to be displayed and the critical section
// protecting it // protecting it
wxArrayString m_messages; wxArrayString m_messages;
@ -208,21 +210,14 @@ enum
class MyThread : public wxThread class MyThread : public wxThread
{ {
public: public:
MyThread(MyFrame *frame); MyThread();
virtual ~MyThread(); virtual ~MyThread();
// thread execution starts here // thread execution starts here
virtual void *Entry(); virtual void *Entry();
// write something to the text control in the main frame
void WriteText(const wxString& text)
{
m_frame->LogThreadMessage(text);
}
public: public:
unsigned m_count; unsigned m_count;
MyFrame *m_frame;
}; };
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -320,12 +315,7 @@ bool MyApp::OnInit()
wxLog::AddTraceMask("thread"); wxLog::AddTraceMask("thread");
// Create the main frame window // Create the main frame window
MyFrame *frame = new MyFrame((wxFrame *)NULL, _T("wxWidgets threads sample"), new MyFrame("wxWidgets threads sample");
50, 50, 450, 340);
SetTopWindow(frame);
// Show the frame
frame->Show(true);
return true; return true;
} }
@ -356,10 +346,11 @@ BEGIN_EVENT_TABLE(MyFrame, wxFrame)
END_EVENT_TABLE() END_EVENT_TABLE()
// My frame constructor // My frame constructor
MyFrame::MyFrame(wxFrame *frame, const wxString& title, MyFrame::MyFrame(const wxString& title)
int x, int y, int w, int h) : wxFrame(NULL, wxID_ANY, title)
: wxFrame(frame, wxID_ANY, title, wxPoint(x, y), wxSize(w, h))
{ {
m_oldLogger = wxLog::GetActiveTarget();
SetIcon(wxIcon(sample_xpm)); SetIcon(wxIcon(sample_xpm));
// Make a menubar // Make a menubar
@ -394,18 +385,45 @@ MyFrame::MyFrame(wxFrame *frame, const wxString& title,
m_nRunning = m_nCount = 0; m_nRunning = m_nCount = 0;
m_dlgProgress = (wxProgressDialog *)NULL; m_dlgProgress = NULL;
#if wxUSE_STATUSBAR #if wxUSE_STATUSBAR
CreateStatusBar(2); CreateStatusBar(2);
#endif // wxUSE_STATUSBAR #endif // wxUSE_STATUSBAR
m_txtctrl = new wxTextCtrl(this, wxID_ANY, _T(""), wxPoint(0, 0), wxSize(0, 0), // create the logging text control and a header showing the meaning of the
// different columns
wxTextCtrl *header = new wxTextCtrl(this, wxID_ANY, "",
wxDefaultPosition, wxDefaultSize,
wxTE_READONLY);
DoLogLine(header, " Time", " Thread", "Message");
m_txtctrl = new wxTextCtrl(this, wxID_ANY, "",
wxDefaultPosition, wxDefaultSize,
wxTE_MULTILINE | wxTE_READONLY); wxTE_MULTILINE | wxTE_READONLY);
wxLog::SetActiveTarget(this);
// use fixed width font to align output in nice columns
wxFont font(wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_TELETYPE,
wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
header->SetFont(font);
m_txtctrl->SetFont(font);
m_txtctrl->SetFocus();
// layout and show the frame
wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(header, wxSizerFlags().Expand());
sizer->Add(m_txtctrl, wxSizerFlags(1).Expand());
SetSizer(sizer);
SetSize(600, 350);
Show();
} }
MyFrame::~MyFrame() MyFrame::~MyFrame()
{ {
wxLog::SetActiveTarget(m_oldLogger);
// NB: although the OS will terminate all the threads anyhow when the main // NB: although the OS will terminate all the threads anyhow when the main
// one exits, it's good practice to do it ourselves -- even if it's not // one exits, it's good practice to do it ourselves -- even if it's not
// completely trivial in this example // completely trivial in this example
@ -432,9 +450,43 @@ MyFrame::~MyFrame()
wxGetApp().m_semAllDone.Wait(); wxGetApp().m_semAllDone.Wait();
} }
void
MyFrame::DoLogLine(wxTextCtrl *text,
const wxString& timestr,
const wxString& threadstr,
const wxString& msg)
{
text->AppendText(wxString::Format("%9s %10s %s", timestr, threadstr, msg));
}
void
MyFrame::DoLogRecord(wxLogLevel level,
const wxString& msg,
const wxLogRecordInfo& info)
{
// let the default GUI logger treat warnings and errors as they should be
// more noticeable than just another line in the log window and also trace
// messages as there may be too many of them
if ( level <= wxLOG_Warning || level == wxLOG_Trace )
{
m_oldLogger->LogRecord(level, msg, info);
return;
}
DoLogLine
(
m_txtctrl,
wxDateTime(info.timestamp).FormatISOTime(),
info.threadId == wxThread::GetMainId()
? wxString("main")
: wxString::Format("%x", info.threadId),
msg + "\n"
);
}
MyThread *MyFrame::CreateThread() MyThread *MyFrame::CreateThread()
{ {
MyThread *thread = new MyThread(this); MyThread *thread = new MyThread;
if ( thread->Create() != wxTHREAD_NO_ERROR ) if ( thread->Create() != wxTHREAD_NO_ERROR )
{ {
@ -447,19 +499,6 @@ MyThread *MyFrame::CreateThread()
return thread; return thread;
} }
void MyFrame::DoLogThreadMessages()
{
wxCriticalSectionLocker lock(m_csMessages);
const size_t count = m_messages.size();
for ( size_t n = 0; n < count; n++ )
{
m_txtctrl->AppendText(m_messages[n]);
}
m_messages.clear();
}
void MyFrame::UpdateThreadStatus() void MyFrame::UpdateThreadStatus()
{ {
wxCriticalSectionLocker enter(wxGetApp().m_critsect); wxCriticalSectionLocker enter(wxGetApp().m_critsect);
@ -623,8 +662,6 @@ void MyFrame::OnPauseThread(wxCommandEvent& WXUNUSED(event) )
void MyFrame::OnIdle(wxIdleEvent& event) void MyFrame::OnIdle(wxIdleEvent& event)
{ {
DoLogThreadMessages();
UpdateThreadStatus(); UpdateThreadStatus();
event.Skip(); event.Skip();
@ -844,11 +881,10 @@ void MyImageDialog::OnPaint(wxPaintEvent& WXUNUSED(evt))
// MyThread // MyThread
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
MyThread::MyThread(MyFrame *frame) MyThread::MyThread()
: wxThread() : wxThread()
{ {
m_count = 0; m_count = 0;
m_frame = frame;
} }
MyThread::~MyThread() MyThread::~MyThread()
@ -873,12 +909,7 @@ MyThread::~MyThread()
wxThread::ExitCode MyThread::Entry() wxThread::ExitCode MyThread::Entry()
{ {
wxString text; wxLogMessage("Thread started (priority = %u).", GetPriority());
text.Printf(wxT("Thread %p started (priority = %u).\n"),
GetId(), GetPriority());
WriteText(text);
// wxLogMessage(text); -- test wxLog thread safeness
for ( m_count = 0; m_count < 10; m_count++ ) for ( m_count = 0; m_count < 10; m_count++ )
{ {
@ -894,16 +925,13 @@ wxThread::ExitCode MyThread::Entry()
if ( TestDestroy() ) if ( TestDestroy() )
break; break;
text.Printf(wxT("[%u] Thread %p here.\n"), m_count, GetId()); wxLogMessage("Thread progress: %u", m_count);
WriteText(text);
// wxSleep() can't be called from non-GUI thread! // wxSleep() can't be called from non-GUI thread!
wxThread::Sleep(1000); wxThread::Sleep(1000);
} }
text.Printf(wxT("Thread %p finished.\n"), GetId()); wxLogMessage("Thread finished.");
WriteText(text);
// wxLogMessage(text); -- test wxLog thread safeness
return NULL; return NULL;
} }

View File

@ -43,6 +43,7 @@
#include "wx/textfile.h" #include "wx/textfile.h"
#include "wx/thread.h" #include "wx/thread.h"
#include "wx/crt.h" #include "wx/crt.h"
#include "wx/vector.h"
// other standard headers // other standard headers
#ifndef __WXWINCE__ #ifndef __WXWINCE__
@ -68,33 +69,53 @@ const char *wxLOG_COMPONENT = "";
#if wxUSE_THREADS #if wxUSE_THREADS
namespace
{
// contains messages logged by the other threads and waiting to be shown until
// Flush() is called in the main one
typedef wxVector<wxLogRecord> wxLogRecords;
wxLogRecords gs_bufferedLogRecords;
// define static functions providing access to the critical sections we use // define static functions providing access to the critical sections we use
// instead of just using static critical section variables as log functions may // instead of just using static critical section variables as log functions may
// be used during static initialization and while this is certainly not // be used during static initialization and while this is certainly not
// advisable it's still better to not crash (as we'd do if we used a yet // advisable it's still better to not crash (as we'd do if we used a yet
// uninitialized critical section) if it happens // uninitialized critical section) if it happens
static inline wxCriticalSection& GetTraceMaskCS() // this critical section is used for buffering the messages from threads other
// than main, i.e. it protects all accesses to gs_bufferedLogRecords above
inline wxCriticalSection& GetBackgroundLogCS()
{
static wxCriticalSection s_csBackground;
return s_csBackground;
}
inline wxCriticalSection& GetTraceMaskCS()
{ {
static wxCriticalSection s_csTrace; static wxCriticalSection s_csTrace;
return s_csTrace; return s_csTrace;
} }
static inline wxCriticalSection& GetPreviousLogCS() inline wxCriticalSection& GetPreviousLogCS()
{ {
static wxCriticalSection s_csPrev; static wxCriticalSection s_csPrev;
return s_csPrev; return s_csPrev;
} }
static inline wxCriticalSection& GetLevelsCS() inline wxCriticalSection& GetLevelsCS()
{ {
static wxCriticalSection s_csLevels; static wxCriticalSection s_csLevels;
return s_csLevels; return s_csLevels;
} }
} // anonymous namespace
#endif // wxUSE_THREADS #endif // wxUSE_THREADS
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -256,6 +277,28 @@ wxLog::OnLog(wxLogLevel level,
if ( !pLogger ) if ( !pLogger )
return; return;
#if wxUSE_THREADS
if ( !wxThread::IsMain() )
{
wxCriticalSectionLocker lock(GetBackgroundLogCS());
gs_bufferedLogRecords.push_back(wxLogRecord(level, msg, info));
// ensure that our Flush() will be called soon
wxWakeUpIdle();
return;
}
#endif // wxUSE_THREADS
pLogger->OnLogInMainThread(level, msg, info);
}
void
wxLog::OnLogInMainThread(wxLogLevel level,
const wxString& msg,
const wxLogRecordInfo& info)
{
if ( GetRepetitionCounting() ) if ( GetRepetitionCounting() )
{ {
wxCRIT_SECT_LOCKER(lock, GetPreviousLogCS()); wxCRIT_SECT_LOCKER(lock, GetPreviousLogCS());
@ -269,7 +312,7 @@ wxLog::OnLog(wxLogLevel level,
return; return;
} }
pLogger->LogLastRepeatIfNeededUnlocked(); LogLastRepeatIfNeededUnlocked();
// reset repetition counter for a new message // reset repetition counter for a new message
gs_prevLog.msg = msg; gs_prevLog.msg = msg;
@ -297,7 +340,7 @@ wxLog::OnLog(wxLogLevel level,
} }
#endif // wxUSE_LOG_TRACE #endif // wxUSE_LOG_TRACE
pLogger->DoLogRecord(level, prefix + msg + suffix, info); DoLogRecord(level, prefix + msg + suffix, info);
} }
void wxLog::DoLogRecord(wxLogLevel level, void wxLog::DoLogRecord(wxLogLevel level,
@ -557,6 +600,32 @@ void wxLog::TimeStamp(wxString *str)
void wxLog::Flush() void wxLog::Flush()
{ {
#if wxUSE_THREADS
wxASSERT_MSG( wxThread::IsMain(),
"should be called from the main thread only" );
// check if we have queued messages from other threads
wxLogRecords bufferedLogRecords;
{
wxCriticalSectionLocker lock(GetBackgroundLogCS());
bufferedLogRecords.swap(gs_bufferedLogRecords);
// release the lock now to not keep it while we are logging the
// messages below, allowing background threads to run
}
if ( !bufferedLogRecords.empty() )
{
for ( wxLogRecords::const_iterator it = bufferedLogRecords.begin();
it != bufferedLogRecords.end();
++it )
{
OnLogInMainThread(it->level, it->msg, it->info);
}
}
#endif // wxUSE_THREADS
LogLastRepeatIfNeeded(); LogLastRepeatIfNeeded();
} }
@ -566,6 +635,8 @@ void wxLog::Flush()
void wxLogBuffer::Flush() void wxLogBuffer::Flush()
{ {
wxLog::Flush();
if ( !m_str.empty() ) if ( !m_str.empty() )
{ {
wxMessageOutputBest out; wxMessageOutputBest out;

View File

@ -53,10 +53,6 @@
#include "wx/arrstr.h" #include "wx/arrstr.h"
#include "wx/msgout.h" #include "wx/msgout.h"
#if wxUSE_THREADS
#include "wx/thread.h"
#endif // wxUSE_THREADS
#ifdef __WXMSW__ #ifdef __WXMSW__
// for OutputDebugString() // for OutputDebugString()
#include "wx/msw/private.h" #include "wx/msw/private.h"
@ -298,6 +294,8 @@ wxLogGui::DoShowMultipleLogMessages(const wxArrayString& messages,
void wxLogGui::Flush() void wxLogGui::Flush()
{ {
wxLog::Flush();
if ( !m_bHasMessages ) if ( !m_bHasMessages )
return; return;
@ -451,14 +449,11 @@ public:
#endif // CAN_SAVE_FILES #endif // CAN_SAVE_FILES
void OnClear(wxCommandEvent& event); void OnClear(wxCommandEvent& event);
// this function is safe to call from any thread (notice that it should be // do show the message in the text control
// also called from the main thread to ensure that the messages logged from void ShowLogMessage(const wxString& message)
// it appear in correct order with the messages from the other threads) {
void AddLogMessage(const wxString& message); m_pTextCtrl->AppendText(message + wxS('\n'));
}
// actually append the messages logged from secondary threads to the text
// control during idle time in the main thread
virtual void OnInternalIdle();
private: private:
// use standard ids for our commands! // use standard ids for our commands!
@ -472,24 +467,9 @@ private:
// common part of OnClose() and OnCloseWindow() // common part of OnClose() and OnCloseWindow()
void DoClose(); void DoClose();
// do show the message in the text control
void DoShowLogMessage(const wxString& message)
{
m_pTextCtrl->AppendText(message + wxS('\n'));
}
wxTextCtrl *m_pTextCtrl; wxTextCtrl *m_pTextCtrl;
wxLogWindow *m_log; wxLogWindow *m_log;
// queue of messages logged from other threads which need to be displayed
wxArrayString m_pendingMessages;
#if wxUSE_THREADS
// critical section to protect access to m_pendingMessages
wxCriticalSection m_critSection;
#endif // wxUSE_THREADS
DECLARE_EVENT_TABLE() DECLARE_EVENT_TABLE()
wxDECLARE_NO_COPY_CLASS(wxLogFrame); wxDECLARE_NO_COPY_CLASS(wxLogFrame);
}; };
@ -602,43 +582,6 @@ void wxLogFrame::OnClear(wxCommandEvent& WXUNUSED(event))
m_pTextCtrl->Clear(); m_pTextCtrl->Clear();
} }
void wxLogFrame::OnInternalIdle()
{
{
wxCRIT_SECT_LOCKER(locker, m_critSection);
const size_t count = m_pendingMessages.size();
for ( size_t n = 0; n < count; n++ )
{
DoShowLogMessage(m_pendingMessages[n]);
}
m_pendingMessages.clear();
} // release m_critSection
wxFrame::OnInternalIdle();
}
void wxLogFrame::AddLogMessage(const wxString& message)
{
wxCRIT_SECT_LOCKER(locker, m_critSection);
#if wxUSE_THREADS
if ( !wxThread::IsMain() || !m_pendingMessages.empty() )
{
// message needs to be queued for later showing
m_pendingMessages.Add(message);
wxWakeUpIdle();
}
else // we are the main thread and no messages are queued, so we can
// log the message directly
#endif // wxUSE_THREADS
{
DoShowLogMessage(message);
}
}
wxLogFrame::~wxLogFrame() wxLogFrame::~wxLogFrame()
{ {
m_log->OnFrameDelete(this); m_log->OnFrameDelete(this);
@ -681,7 +624,7 @@ void wxLogWindow::DoLogTextAtLevel(wxLogLevel level, const wxString& msg)
if ( level == wxLOG_Trace ) if ( level == wxLOG_Trace )
return; return;
m_pLogFrame->AddLogMessage(msg); m_pLogFrame->ShowLogMessage(msg);
} }
wxFrame *wxLogWindow::GetFrame() const wxFrame *wxLogWindow::GetFrame() const