Printing in a window from a separate thread


#1

Hi, I am having a problem displaying text in a main console window when its display method is called from a separate thread. im guessing this is a simple problem to fix but I dont know how to solve it! the mis-behavior is sometimes not seeing a message when its printed (Im certain the message text exists because i can print it to standard out and it appears if i click inside the console window) and perhaps crashing the app occasionally (im not sure this mis-behavior is the culprit yet…)

the background: my main app has a Console window that contains a Juce TextEditor as a read/write buffer. The console window has a method ‘void display(String text)’ that the other components in the App can call on to insert text messages into the buffer so users can see them.

now, when other GUI windows such as our Text editor have the focus and then call console->display() it works fine. However, the main use of the console is to print message from a JUCE thread without a gui component that is running Chicken Scheme. The purpose of this thread is to get (lisp) text from our Text Editor, eval it in Chicken Scheme and then print the results to Console. The communication of the JUCE thread with Chicken Scheme seems to be worksing fine and I can always print Scheme’s results to standard out (Terminal.app on MacOSX) wihtout any problems.

However, if the Scheme thread calls console->display() to print the text in the Console window then it MOSTLY works, but sometimes only the first character of the message appears. If I then go and click inside the console window then the text appears. (on rare occasions i think the app gets completly hosed but im not sure if its related to the printing problem yet)

Ive tried making display() call repaint() explicitly but this doesnt seem to fix anything. I am always inserting new messages at the very end of the buffer if that matters. any help appreciated!

–rick taube


this is what my display() function looks like:

void ConsoleWindow::display(String str, ConsoleTheme::ColorType colr) {
const MessageManagerLock mmLock;
console->lock->enter();
setConsoleTextColor(colr);
console->buffer->setCaretPosition(0xFFFFFF); // go to EOB
console->buffer->insertTextAtCursor(str);
console->lock->exit();
}


this is what a call to display() looks like from inside the scheme thread. the printf’s work fine.

{
  res = CHICKEN_eval_string_to_string( (char *)expr.toUTF8(),
				   schemeThread->evalBuffer,
                                       8192);
  if ( res==0 ) {
    CHICKEN_get_error_message(schemeThread->errorBuffer, 8192);
String text=T(">>> ") + T(String(schemeThread->errorBuffer));
    printf("%s\n", text.toUTF8());   // log to terminal
schemeThread->console->display(text, errorColor);
  }
  else {
printf("Returned: %s\n", schemeThread->evalBuffer); // log to term
schemeThread->console->display(String(schemeThread->evalBuffer), valuesColor);
  }
}

#2

Is your console window a JUCE component (text editor for example) or a MacOS component?

If it’s a JUCE component, you’re probably making life a little harder than you need. If the component (or perhaps an interface layer to it) derives from AsyncUpdater, you can call the update method on any thread, but handle it safely in the message thread.


#3

Is your console window a JUCE component (text editor for example) or a MacOS component?

thanks for your reply, my console window inherits from DocumentWindow (see below), it uses a TextEditor as the buffer for displaying messages. the asyncUpdate looks like what i need but dont see how i can pass it the text to display for the callback to insert into the buffer. however that led me to the MessageListener class and the handleMessage method. so I tried defining a test method:

void ConsoleWindow::handleMessage(const Message &msg) {
printf(“In handle message, id=%d!\n”, msg.intParameter1 );
}

and then calling a test postMessage from the Scheme thread:

schemeThread->console->postMessage(new Message(-99, 0, 0, NULL));

BUT the compiler doenst like this, it complains about the method being ‘inaccessible’:

g+±3.3 -o build/Scheme.o -c -DMACOSX -DNDEBUG -DSCHEME -Isrc -I/usr/local/src/juce-1.45 src/Scheme.cpp
/usr/local/src/juce-1.45/src/juce_appframework/events/juce_MessageListener.h: In
member function bool SchemeNode::process(double)': /usr/local/src/juce-1.45/src/juce_appframework/events/juce_MessageListener.h:81: error:
void juce::MessageListener::postMessage(juce::Message*) const’ is
inaccessible


from the docs it seems that Windows are already Listeners. But I tried adding MessageLisntener as superclass anyway but that gave other errors about it being ‘ambiguous’ .

so what do i need to do so that I can call consolwin->postMessage() from my scheme thread? or am i misundertstanding how this work?

thank for any help,

– Rick Taube


#4

I think I’d probably go about this in a different way (though obviously this is preference thing).

Why not make the texteditor an async updater, and give it a method like:

addLine(const String& newLine)
{
_myCriticalSection.enter();
_myBuffer.add(newLine);
_myCriticalSection.exit();
}

that is threadsafe, and simply adds a line of text to an internal buffer.

When the async callback occurs, the component can copy the line(s) in the buffer to the texteditor itself. This’ll be in the message thread, so it’ll be safe, and will generate a safe repaint().

So adding lines from your other thread would simply be a case of calling:

console.addLine(“some text”);
console.triggerAsyncUpdate();

This also has the advantage that multiple lines can be added before calling the trigger, so further reducing the number of unnecessary messages and repaint requests.

You certainly could use custom messaging, but I think in the long run, that’ll be more code, and more complexity than simply throwing in the small extra overhead of an intermediate buffer.


#5

Hi i implemented the async updating as you suggested on my console window, i can now post messages from the scheme thread and then call handleAsycnUpdate(). i think this is much better than the way I had it, thank you.

unfortunatly it doesnt stop the display problem – sometimes only the first character appears in the newly printed line until i either (1) click in the console window or (2) trigger ANOTHER print + async update. In either case the missing text then appears in the window along with whatever new text get printed. (ive included new update for the console thread in case you see something wrong at the end of this message)

another clue: if the printout is longer than a line then sometimes the “wapped” portion doesnt appear until I either do (1) or (2).
Ive tried always inserting text at 0 in the TextEditor to see if that fixes anything but it doesnt.

im stumped! maybe the problem is with the TextEditor itself?? if so perhaps there is there some other component that i could use that could act as a console ? My console window doesnt have to support full blown editing - it just has to display LOTS of scrollable, colorized text that can be copied with the mouse and pasted to text editing windows…


void ConsoleWindow::postConsoleTextMessage(String msg, int color) {
// add a message to the pending message buffer
messages.lockArray();
messages.add(new ConsoleMessage(typ, color));
messages.unlockArray();
}

void ConsoleWindow::handleAsyncUpdate( ) {
printf(“In handle async update\n”);
for (int i=0; i<messages.size(); i++)
switch ( messages[i]->color ) {
case ConsoleMessage::TEXT :
printMessage( messages[i]->text );
break;
case ConsoleMessage::VALUES :
printValues( messages[i]->text );
break;
case ConsoleMessage::WARNING :
printWarning( messages[i]->text );
break;
case ConsoleMessage::ERROR :
printError( messages[i]->text );
break;
default:
break;
}
messages.lockArray();
messages.clear();
messages.unlockArray();
}

this is how i call it from the scheme thread:

schemeThread->console->postConsoleTextMessage( T(“Hiho!”), ConsoleMessage::ERROR);
schemeThread->console->handleAsyncUpdate();

[/b]


#6

I’m guessing this is just a typo, right?

I can’t see anything obviously awry in the code your gave (other than the typo above). Perhaps better eyes than mine will spot something.

What do your printMessage() / values() etc methods do?

I’ve never really used the text editor component for anything other than trivial single line entry boxes, so I don’t know of any gotchas it might have, but I think in general it should be an ideal component for what you are doing.

This is bound to just be a subtle error somewhere.


#7

=8O
no thats not a typo, that was my mistake. but changing it to triggerAsyncMessage() give me the same compiler error I had before:

/usr/local/src/juce-1.45/src/juce_appframework/events/juce_AsyncUpdater.h: In
member function void SchemeThread::reportChickenError(juce::String)': /usr/local/src/juce-1.45/src/juce_appframework/events/juce_AsyncUpdater.h:73: error:
void juce::AsyncUpdater::triggerAsyncUpdate()’ is inaccessible
src/Scheme.cpp:161: error: within this context
scons: *** [build/Scheme.o] Error 1


So clearly Im not understanding something about how to use the Async class. Im defining the voidHandleAsyncMessage on the WINDOW, maybe this isnt legal?


#8

It’s possible you’re inheriting from asu on a class that already inherits from it in a private scope.

It’s hard to call without knowing more about your console class.

If one of the base classes does already inherit from the async updater then I guess you could just do something like:

handleAsyncUpdate()
{
if (messageBuffer.isEmpty()) parentClass::handleAsyncUpdate();
else
{
// do your buffer-text editor copying stuff here.
}
}

be sure to redeclare the handeAsyncUpdate() method as public, obviously.

If this isn’t the problem, then unless someone else can chim in with some blindingly obvious that isn’t occurring to me, could you post details of the console class, and what it derives from.


#9

Hi, I made my TextEditor component inherit from AsyncUpdater (instead of my Console window) and posting messages from the Scheme thread seems to be working now! thanks very much for your helpful advice, i would have flailed around for quite some time without it!

best, rick taube


#10