Fl::awake and Fl::add_timeout

88 views
Skip to first unread message

geert karman

unread,
Oct 24, 2022, 3:38:18 AM10/24/22
to fltk.general
Hello, 

I am working on a multithreaded application with a FLTK GUI. 

On MacOS, certain changes to widgets can only be made from the main thread (changes to the menu bar for example), not from worker threads. When I see the need for such a change from within a worker thread I can signal the main thread with a Fl::awake(handler,void*) call. However, in my case this occurs in functions that may both be called from the main thread as well as from worker threads. I could of course figure out what thread I am in and either call Fl::awake or do the change to the menu directly, but is it possible to call Fl::awake also from the main thread? (Just to keep things more simple.)

As an alternative, could I use Fl::add_timeout with a very small delay time? As far as I can see there is no problem in adding a timeout from a worker thread, or is there?

Thanks for the help.

Ian MacArthur

unread,
Oct 24, 2022, 4:17:22 AM10/24/22
to fltk.general
On Monday, 24 October 2022 at 08:38:18 UTC+1 geert karman wrote:
Hello, 

I am working on a multithreaded application with a FLTK GUI. 

On MacOS, certain changes to widgets can only be made from the main thread (changes to the menu bar for example), not from worker threads. When I see the need for such a change from within a worker thread I can signal the main thread with a Fl::awake(handler,void*) call. However, in my case this occurs in functions that may both be called from the main thread as well as from worker threads. I could of course figure out what thread I am in and either call Fl::awake or do the change to the menu directly, but is it possible to call Fl::awake also from the main thread? (Just to keep things more simple.)

TBH, what I do is record the thread ID of the main thread *very soon* after my app starts, and before I start any of my worker threads, and make that value available to be read via a global method. In the callbacks and methods that might be called from either context, I compare the current thread ID with this "master" ID and if they match I do not call the awake() wrappers, just call the CB operation directly. If the threads do not match, I use the awake() wrapper of course.
The fetching of the thread ID needs a wee bit of platform-specific code (Win vs pthreads) but I have that all bundled up in a few function calls.

Now: whether or not this is *necessary* I am not sure - I suspect that actually it is not necessary at all and that the awake() wrappers will work fine from the main() thread, but I'm not entirely sure so have always erred on the side of caution. The cost of doing the checks in the CB wrappers is possibly less that the cost of using the awake() wrapper from the main thread, so overall it is probably not costing me all that much - and at least I know it works!


As an alternative, could I use Fl::add_timeout with a very small delay time? As far as I can see there is no problem in adding a timeout from a worker thread, or is there?

No, don't. On several platforms (possibly not OSX/macOS, not sure) the timers need a valid window handle to hang onto, so creating/deleting them from a non-main thread can lead to corruption of the window stack. This is documented (see, for example the FLTK multithreaded Constraints section of https://www.fltk.org/doc-1.3/advanced.html and etc.)

 

Albrecht Schlosser

unread,
Oct 24, 2022, 11:54:57 AM10/24/22
to fltkg...@googlegroups.com
On 10/24/22 09:38 geert karman wrote:
>
> ... is it possible to call Fl::awake also from the main thread? (Just
> to keep things more simple.)

Fl::awake() sends a system message to the main thread which should be
possible from both a worker and the main thread itself. As always you
should have called Fl::lock() in the main thread's startup code.

Disclaimer: I checked the code and I didn't see a reason that it
wouldn't be correct, hence my statement above is probably true for the
current code in 1.4.0 (master). However, this statement is not
guaranteed to be true (at all and/or) in the future, as long as it is
not documented.

> As an alternative, could I use Fl::add_timeout with a very small delay
> time? As far as I can see there is no problem in adding a timeout from
> a worker thread, or is there?

As Ian wrote, on some platforms (particularly Windows and macOS) there
are system timers involved that may depend on a window and this may
cause trouble if you call it from a worker thread. This is true for all
FLTK versions up to 1.3.x.

In FLTK 1.4.0 the timeout handling on all platforms does no longer use
system timers, i.e. there are no definitely no system windows involved.
As long as you call Fl::lock() before Fl::add_timeout() and Fl::unlock()
after it you should be fine. Calling Fl::lock/unlock() in the main
thread should do no harm because FLTK uses recursive locks but it's an
overhead.

Calling Fl::awake(), OTOH, does not call the full FLTK lock (it locks
only the awake message ring) which should be faster because FLTK would
hold the lock only for a negligible time and this wouldn't lock up your
threads, waiting until the FLTK main thread has done its work while it
holds the main FLTK lock (event handling and drawing).

Hence I would say that calling Fl::awake(handler, xxx) would be your
best choice, and if you could avoid doing this in the main thread (as
Ian suggested) this would be even better.

Disclaimer: no guarantees.

Bill Spitzak

unread,
Oct 24, 2022, 12:20:53 PM10/24/22
to fltkg...@googlegroups.com
I would say that if FL::awake does not work from the main thread, it is a bug.
I would fix this in whatever way uses the smallest amount of code. So first check if what it is doing already works (quite likely). If not, detect the main thread and call the callback directly before FL::awake returns.

--
You received this message because you are subscribed to the Google Groups "fltk.general" group.
To unsubscribe from this group and stop receiving emails from it, send an email to fltkgeneral...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/fltkgeneral/c5589888-207d-7b13-3d14-ba97c8472a6e%40online.de.

geert karman

unread,
Oct 25, 2022, 4:16:53 AM10/25/22
to fltk.general


Op maandag 24 oktober 2022 om 17:20:53 UTC+1 schreef spitzak:
I would say that if FL::awake does not work from the main thread, it is a bug.
I would fix this in whatever way uses the smallest amount of code. So first check if what it is doing already works (quite likely). If not, detect the main thread and call the callback directly before FL::awake returns.



I am indeed using FLTK 1.4 because I need some of the latest changes. As Ian points out, the manual says that one should avoid calling timers from working threads, but it does not mention that this has changed in version 1.4 (page 139 of the FLTK 1.4.0 Programming manual). Maybe this should be updated? I readily admit that the manual is sometimes (not always ;)) wasted on me, but at least in this case I learnt something new.

I just checked if Fl::awake(handler, xxx) works from the main thread and it seems it does. If as Bill says this should be working, will it then be an official feature so that I can start using it? Maybe that should then also be included in the manual?

True that detecting the main thread is not a problem (especially with std::this_thread::get_id() which should work on all platforms) but I have the impression that I am getting a lot of intermediate functions to handle this kind of stuff, so just calling Fl::awake and let FLTK do it would help a little in refactoring.



Matthias Melcher

unread,
Oct 25, 2022, 5:23:50 AM10/25/22
to fltk.general
geert karman schrieb am Dienstag, 25. Oktober 2022 um 10:16:53 UTC+2:
Op maandag 24 oktober 2022 om 17:20:53 UTC+1 schreef spitzak:
I would say that if FL::awake does not work from the main thread, it is a bug.

I just checked if Fl::awake(handler, xxx) works from the main thread and it seems it does. If as Bill says this should be working, will it then be an official feature so that I can start using it? Maybe that should then also be included in the manual?

Fl::awake(handler,xxx), just like Fl::awake(), originally just added a message to the message queue of the main thread. There should be no reason why this can't be called from within the main thread. Adding a message will keep consecutive awake() call in the correct order, and will be run before idle() call-backs.

But I wrote this sooo many moons ago that I don't know how that code was implemented on other platforms and how the implementation may have changed over time.

If Bill say that it has a bug, we should fix it, because it is a really nice way to defer actions into the main thread. Maybe we should at some point start some real unit testing to find platform differences and regressions? I am an old fart by now and I never really had to implement unit testing in any of my projects.

Ian MacArthur

unread,
Oct 25, 2022, 5:25:04 AM10/25/22
to fltk.general
On Tuesday, 25 October 2022 at 09:16:53 UTC+1 geert karman wrote:

I am indeed using FLTK 1.4 because I need some of the latest changes. As Ian points out, the manual says that one should avoid calling timers from working threads, but it does not mention that this has changed in version 1.4 (page 139 of the FLTK 1.4.0 Programming manual). Maybe this should be updated? I readily admit that the manual is sometimes (not always ;)) wasted on me, but at least in this case I learnt something new.

I'm not keen on changing the manual for this, at least not at this stage, as it would break potential portability. As things stands, code "written for" 1.4 can generally be compiled and run just fine with 1.3 too, but if folks write code that depends on starting/stopping timers from a worker thread, that will certainly not be "backwards compatible", and might get a bit "crashy"...
 

I just checked if Fl::awake(handler, xxx) works from the main thread and it seems it does. If as Bill says this should be working, will it then be an official feature so that I can start using it? Maybe that should then also be included in the manual?

I don't think there was ever that much doubt that it would work, it was expected that it would, rather the concern is that it would be unnecessarily complex and potentially inefficient to do it that way, such that skipping the awake() mechanism (if called form the main() thread) would be more effective.
In effect, the awake() mechanism is locking the main thread, adding the CB to the handler queue, unlocking the main thread... Then at some indeterminate later time the main thread processes the CB handler queue and runs the CB. But the main thread could basically just execute the CB directly, and miss out the queue and the locking/unlocking of the thread. And anything that avoids unnecessary locks is a Good Thing on a modern multicore CPU...
 
True that detecting the main thread is not a problem (especially with std::this_thread::get_id() which should work on all platforms) but I have the impression that I am getting a lot of intermediate functions to handle this kind of stuff, so just calling Fl::awake and let FLTK do it would help a little in refactoring.
 
Indeed - but in the immediate term, it's likely easiest to just call the awake() regardless, or do your own check... I do my own check...


Albrecht Schlosser

unread,
Oct 25, 2022, 12:06:37 PM10/25/22
to fltkg...@googlegroups.com
On 10/24/22 18:20 Bill Spitzak wrote:
> I would say that if FL::awake does not work from the main thread, it
> is a bug.

Nobody said that it doesn't work. I only mentioned that it is not
guaranteed to work as long as it is not documented.

So my take on this is, if Bill's statement is the originial intention,
then we should document it, and then it must be guaranteed to work in
the future.

> I would fix this in whatever way uses the smallest amount of code. So
> first check if what it is doing already works (quite likely).

I'm pretty sure it does work.

> If not, detect the main thread and call the callback directly before
> FL::awake returns.

If we did this, then the sequence of processing Fl::awake() calls could
be reordered, for instance:

In clock time sequence:

Worker thread: Fl::awake(proc, (void *)1);

Main FLTK thread: Fl::awake(proc, (void *)2);

... *might* cause `proc` to be called twice with arguments `2` and `1`
in this order. This could happen because the worker thread would *queue*
the message whereas the later call in the main thread would be executed
immediately before the queued message of the worker thread would be
processed. This can happen because the worker thread doesn't need to
call Fl::lock() to call Fl::awake().

IMHO the only real solution is to make sure it works (very likely) and
document it as a guaranteed property (to be done).

Albrecht Schlosser

unread,
Oct 25, 2022, 12:41:03 PM10/25/22
to fltkg...@googlegroups.com
On 10/25/22 11:25 Ian MacArthur wrote:
On Tuesday, 25 October 2022 at 09:16:53 UTC+1 geert karman wrote:

I am indeed using FLTK 1.4 because I need some of the latest changes. As Ian points out, the manual says that one should avoid calling timers from working threads, but it does not mention that this has changed in version 1.4 (page 139 of the FLTK 1.4.0 Programming manual).

Geert, could you please give more context of the text where you found this? I don't see this on "page 139" (note that the PDF version numbers can change from version to version, if that's what you meant with "page 139". I searched the docs for "timeout" (not "timer") but didn't find anything related to what you said.

I'm asking because I'd like to improve the manual as much as possible.


Maybe this should be updated? I readily admit that the manual is sometimes (not always ;)) wasted on me, but at least in this case I learnt something new.

I'm not keen on changing the manual for this, at least not at this stage, as it would break potential portability.

Ian, I'm always for making clear statements in the manual. If starting timers from a worker thread is possible in FLTK 1.4 (and I'm sure it is because I wrote the new code) then it should be documented. We should certainly document that this is a new quality in 1.4 and that this is not possible in 1.3.x.


As things stands, code "written for" 1.4 can generally be compiled and run just fine with 1.3 too, but if folks write code that depends on starting/stopping timers from a worker thread, that will certainly not be "backwards compatible", and might get a bit "crashy"...

If folks start writing code that is not compatible with 1.3.x then that may be a legit choice, and as you read above, Geert wrote that he needs some new FLTK 1.4 stuff that is obviously not in 1.3. Hence it should be possible to do what he (as an example) likes with FLTK 1.4 in mind since his program would not be backwards compatible anyway.

geert karman

unread,
Oct 25, 2022, 1:16:01 PM10/25/22
to fltk.general

Geert, could you please give more context of the text where you found this? I don't see this on "page 139" (note that the PDF version numbers can change from version to version, if that's what you meant with "page 139". I searched the docs for "timeout" (not "timer") but didn't find anything related to what you said.

One of the bullet points in section 14.5, "FLTK multithreaded Constraints" where it simply says: "Don't start or cancel timers from a worker thread".

I really appreciate it to have the possibility to call Fl::awake from the main thread as a property of FLTK 1.4. Thanks a lot!
 

Albrecht Schlosser

unread,
Oct 25, 2022, 1:43:03 PM10/25/22
to fltkg...@googlegroups.com
On 10/25/22 19:16 geert karman wrote:


Geert, could you please give more context of the text where you found this? I don't see this on "page 139" (note that the PDF version numbers can change from version to version, if that's what you meant with "page 139". I searched the docs for "timeout" (not "timer") but didn't find anything related to what you said.

One of the bullet points in section 14.5, "FLTK multithreaded Constraints" where it simply says: "Don't start or cancel timers from a worker thread".

OK, found it, thanks. As I wrote, I looked for "timeout" rather than "timer". I should have tried this as well.


I really appreciate it to have the possibility to call Fl::awake from the main thread as a property of FLTK 1.4. Thanks a lot!

It's not yet decided finally because it needs to be verified that it's OK now although this is very likely. It would also need some documentation updates (as you know).

Albrecht Schlosser

unread,
Oct 25, 2022, 2:28:49 PM10/25/22
to fltkg...@googlegroups.com
Note: I opened GitHub Issue #524 <https://github.com/fltk/fltk/issues/524> so this will not be forgotten and included in 1.4.0 docs in one or the other way. Hopefully all will be OK and documented accordingly.

Reply all
Reply to author
Forward
0 new messages