Use worker thread to call Fl_Window and Fl_run

51 views
Skip to first unread message

anmol....@gmail.com

unread,
Sep 30, 2020, 12:49:43 PM9/30/20
to fltk.general
Hi. I want to create a simple progress bar, and do not need any user input. I cannot block my main app as it already has its own GUI.
What will happen if I use the following from a worker thread ?

Fl_Window win(220,90);
win.begin(); // add progress bar to it..
Fl_Progress *progress = new Fl_Progress(10,50,200,30);
progress->minimum(0); // set progress range to be 0.0 ~ 1.0
progress->maximum(1);
progress->color(0x88888800); // background color
progress->selection_color(0x4444ff00); // progress bar color
progress->labelcolor(FL_WHITE); // percent text color 50 
win.end();
win.show();
Fl::run();

And I update it from main thread.
progress->value(t/500.0); 
progress->label(percent); // update progress bar's label
Fl::check();

I read the docs, and its clear that the main thread owns the window, but if I dont care about user input, is there any harm in this ?

anmol....@gmail.com

unread,
Sep 30, 2020, 12:54:36 PM9/30/20
to fltk.general
Alternatively is there a non-blocking Fl::run() - basically I do not want my main thread to block.
So perhaps I can start the window() and run() and return immediately so the GUI does not block
till the window is closed.

Ian MacArthur

unread,
Sep 30, 2020, 4:54:25 PM9/30/20
to fltkg...@googlegroups.com
On 30 Sep 2020, at 17:49, anmol.mishra wrote:
>
> Hi. I want to create a simple progress bar, and do not need any user input. I cannot block my main app as it already has its own GUI.
> What will happen if I use the following from a worker thread ?



> I read the docs, and its clear that the main thread owns the window, but if I dont care about user input, is there any harm in this ?


It’s not about user input - the issue is what process, and what thread within that process, owns the context that writes to the GPU...

Now, with more modern GPU drivers, and operating systems, that’s not as fragile as it once was, but basically the *safe* thing to do is handle GUI context from the main thread, always.

Trying to create windows from a subsidiary thread is likely to lead to trouble in strange and hard to debug ways...

However, the key here is that you do not need to call Fl::run() at all, if you only want to drive the GUI from your main thread: all you have to do is call Fl::wait(0); every now and then (say every 100ms or so if you just want to update the GUI but not handle user input...)

So if you can create and show all the window objects in your main thread, then arrange for Fl::wait() to be called periodically from the main thread, that ought to just about do the job.




Greg Ercolano

unread,
Sep 30, 2020, 5:09:34 PM9/30/20
to fltkg...@googlegroups.com
On 2020-09-30 13:54, Ian MacArthur wrote:
> However, the key here is that you do not need to call Fl::run() at all, if you only want to drive the GUI from your main thread: all you have to do is call Fl::wait(0); every now and then (say every 100ms or so if you just want to update the GUI but not handle user input...)
I believe we have an example of this in examples/progress-simple.cxx, which shows:

[..]
// Computation loop needing an updating progress indicator..
for ( int t=1; t<=500; t++ ) {
progress->value(t/500.0); // update progress bar with 0.0 ~ 1.0 value
char percent[10];
sprintf(percent, "%d%%", int((t/500.0)*100.0));
progress->label(percent); // update progress bar's label
Fl::check(); // give fltk some cpu to update the screen
usleep(1000); // <-- your compute intensive stuff goes here
}
[..]

Paul Hahn

unread,
Sep 30, 2020, 5:11:20 PM9/30/20
to fltk.general
So if you can create and show all the window objects in your main thread, then arrange for Fl::wait() to be called periodically from the main thread, that ought to just about do the job.
 
Yes I did that when I wrote an animated "wait window widget" based on a threading solution. A lot of bad things happened unexpectedly until I finally got a handle on how to deal with ensuring FLTK updates happen only in the main thread.

In retrospect, all my initial problems were due to not respecting the FLTK documentned warning to use their lock mechanism for multi-threaded solutions. I definitely respect that now, even though I wound up probably sort of duplicating what it does in my own way (i.e., my own version of Fl::run() and Fl::awake() only implemented with Bohm-atomic "mutexes"). I would utilize the FLTK locking mechanism if I had to do it all over again.

Paul Hahn

unread,
Sep 30, 2020, 5:32:07 PM9/30/20
to fltk.general
In case it might be of interest, I have also had success doing FLTK things from the main thread in a parent process and the main thread in a child subprocess(es) that are coordinating via a 2-way Unix pipe. Effectively, multiple FLTK heavyweight threads doing graphics operations in a coordinated way. Actually performs well. Of course, this is on Linux. I believe this obviates any need for locking as is required for lightweight (same process) threads.

anmol....@gmail.com

unread,
Oct 1, 2020, 5:07:49 AM10/1/20
to fltk.general
Forking a new process may be a bit heavyweight for my needs. Its more intensive on Windows than on *NIX.
I looked at the progress_simple example, and ended up using check() - which is wait(0).

static int AEC_progress = 0;

// Progress callback updates label
void Update_CB(void* userdata) {
    Fl_Progress* progress = (Fl_Progress*)userdata;
    int val;
    val = AEC_progress;
    progress->value((float)val);              // update progress bar with 0.0 ~ 100 value
    char percent[10] = { "" };
    sprintf_s(percent, "%d%%", val);
    progress->label(percent);
    Fl::repeat_timeout(2.0, Update_CB, (void*)progress);
}

int main()
{
    float mult = 100.f / (total_frames-1);
    Fl_Window win(220, 90);          // create parent window

    win.begin();                                // add progress bar to it..
    Fl_Progress progress (10, 50, 200, 30);
    progress.minimum(0);                      // set progress range to be 0.0 ~ 100
    progress.maximum(100);
    progress.color(0x88888800);               // background color
    progress.selection_color(0x4444ff00);     // progress bar color
    progress.labelcolor(FL_WHITE);            // percent text color
    progress.label("0%%");
    win.end();
    Fl::add_timeout(2.0, Update_CB, (void*)&progress);
    win.show();

// Loop
AEC_progress = (int)(i * mult);
        Fl::check();
// End Loop
}

This works well with the bar indicator, but the text shows some junk. Also, this window goes to the background.

Any suggestions to force this window to the top of all other windows? My GUI (code that I cannot change) may be pushing a cmd window to the top as I am using popen/pclose and windows starts a cmd window when you use pipes.

lifeatt...@gmail.com

unread,
Oct 1, 2020, 9:39:47 AM10/1/20
to fltk.general
I've had some success with a message-based system.

In the forked process I invoke Fl::handle_() with a custom message code. In the main code I establish a handler using Fl::add_handler(). Fl::run() operates as normal.  Something like:

int progress_update(int event)
{
    progress->value((float)event_data); // using FL's event_data global for data transfer
    char percent[10] = {""};
    sprintf_s(percent, "%d%%", (int)event_data);
    progress->copy_label(percent);
}

int main()
{
    Fl::add_handler(progress_update);
// create window as shown earlier
    Fl:run()
}

and in the worker thread:
    event_data = t / 500.0;
    Fl::handle_(PROGRESS_UPDATE,0);


anmol....@gmail.com

unread,
Oct 1, 2020, 12:49:46 PM10/1/20
to fltk.general
I've got a popen and pclose setup already going on. And I'm trying to avoid starting other processes ATM, however I'd love to see your implementation - perhaps an example posted in this thread or a new one ?

On Thursday, October 1, 2020 at 3:02:07 AM UTC+5:30 pdh...@compintensehpc.com wrote:

anmol....@gmail.com

unread,
Oct 1, 2020, 12:52:08 PM10/1/20
to fltk.general
Wouldnt Fl::run() block the main loop though ? Thats what I am trying to avoid as its a loop that processes lots of data.

lifeatt...@gmail.com

unread,
Oct 1, 2020, 2:42:52 PM10/1/20
to fltk.general
>> I've had some success with a message-based system.
>Wouldnt Fl::run() block the main loop though ? Thats what I am trying to avoid as its a loop that processes lots of data.  

I must have missed that requirement. The model I'm using is the data processing is in a sub-process, not the main loop. The "historical" model for GUI programs is the main process is the GUI handler thread, and if there is significant, potentially blocking activity, it takes place in secondary threads.

I'll try to whack up a minimal example.

lifeatt...@gmail.com

unread,
Oct 1, 2020, 2:47:31 PM10/1/20
to fltk.general
> The "historical" model for GUI programs is the main process is the GUI handler thread, and if there is significant, potentially blocking activity, it takes place in secondary threads. 

Tho, thinking about it for 5 seconds, I don't see why the GUI thread couldn't be a secondary thread ... 

Greg Ercolano

unread,
Oct 1, 2020, 3:10:15 PM10/1/20
to fltkg...@googlegroups.com
> Any suggestions to force this window to the top of all other windows? My GUI (code that I cannot change) may be pushing a cmd window to the top as I am using popen/pclose and windows starts a cmd window when you use pipes.

You won't have luck using popen() on windows, it's a very weak implementation.

It's best to reimplement its functionality using a combo of WIN32 API functions
CreatePipe() to create the in & out pipes, and assign those to STARTUPINFO
as the hStdInput and hStdOutput/hStdError members, and then pass the STARTUPINFO
to CreateProcess().

It's a bit of work to do so, but I've found it to work reliably, and no DOS window.

--- snip
PROCESS_INFORMATION pi;
memset(&pi, 0, sizeof(pi));

[..]

HANDLE stdinReadPipe = 0;
HANDLE stdinWritePipe = 0;
if ( CreatePipe(&stdinReadPipe, &stdinWritePipe, &sa, 0) == 0 ) { ..error.. }

HANDLE stdoutReadPipe = 0;
HANDLE stdoutWritePipe = 0;
if ( CreatePipe(&stdoutReadPipe, &stdoutWritePipe, &sa, 0) == 0 ) { ..error.. }

STARTUPINFO si;
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
si.wShowWindow = SW_HIDE; // <-- yoo hoo
si.hStdInput = stdinReadPipe;
si.hStdOutput = stdoutWritePipe;
si.hStdError = stdoutWritePipe;

[..]

if (CreateProcess(NULL, // app name
(char*)cmd, // command to exec
NULL, // proc secure attribs
NULL, // thread secure attribs
TRUE, // handle inheritence
CREATE_NEW_PROCESS_GROUP, // creation flags
NULL, // environ block
NULL, // current dir
&si, // startup info
&pi)) // process info
{
// OK - Parent: close pipes we're not interested in.
// Only interested in stdoutReadPipe to get stdout/err from child.
//
if ( stdinReadPipe != 0 ) { CloseHandle(stdinReadPipe); stdinReadPipe = 0; fdstdin = 0; } // fdstdin + stdinReadPipe same
if ( stdinWritePipe != 0 ) { CloseHandle(stdinWritePipe); stdinWritePipe = 0; }
if ( stdoutWritePipe != 0 ) { CloseHandle(stdoutWritePipe); stdoutWritePipe = 0; }

[..]
}
else
{
..error..
if ( stdinReadPipe != 0 ) { CloseHandle(stdinReadPipe); stdinReadPipe = 0; }
if ( stdinWritePipe != 0 ) { CloseHandle(stdinWritePipe); stdinWritePipe = 0; }
if ( stdoutWritePipe != 0 ) { CloseHandle(stdoutWritePipe); stdoutWritePipe = 0; }
}

--- snip

Paul Hahn

unread,
Oct 1, 2020, 4:05:09 PM10/1/20
to fltk.general
> The "historical" model for GUI programs is the main process is the GUI handler thread, and if there is significant, potentially blocking activity, it takes place in secondary threads. 

Tho, thinking about it for 5 seconds, I don't see why the GUI thread couldn't be a secondary thread ... 


In my case, GUI processing is in the main thread in each of the processes (parent and forked child), which keeps things happy for normal FLTK run time processing. Messages are handled separately by a secondary (listener) thread on each side. Interprocess communication (IPC) is via the Unix pipe(2) 2-way link (read & write file descriptor pairs) between the processes. Outgoing messages are typically originated from within FLTK callbacks in a non-blocking way. Incoming messages are enqueued by the listener thread on the other end. These messages are interpreted in the main loop on that end via an FLTK idle handler. I take care on both ends to make sure things don't block in ways that might result in a deadlock (livelock). The message queuing and asynchronicty between the processes might slow things down, but in practice, it is fast enough for my purposes, at least on Linux (my only platform).

Actually, in the specific case of my animated "wait window widget", the child process does all the animation graphics, and I modify the above scheme so the launcher (parent process) by default does not have a separate listener thread, but itself blocks waiting for the child to emit "finished" (e.g., which happens when Cancel button is pressed, processing reaches completion, the process dies unexpectedly, etc.).

Ian MacArthur

unread,
Oct 1, 2020, 4:22:09 PM10/1/20
to fltkg...@googlegroups.com
On 1 Oct 2020, at 14:39, lifeattickville wrote:
>
> I've had some success with a message-based system.
>
> In the forked process I invoke Fl::handle_() with a custom message code. In the main code I establish a handler using Fl::add_handler(). Fl::run() operates as normal. Something like:


Hmmm... OK...

TBH, I think I’d just use the Fl::awake() mechanism to pass callbacks to the main thread to be processed, as that is how we envisaged doing this TBH...


Ian MacArthur

unread,
Oct 1, 2020, 4:24:03 PM10/1/20
to fltkg...@googlegroups.com
On 1 Oct 2020, at 19:42, lifeattickville wrote:
>
> >> I've had some success with a message-based system.
> >Wouldnt Fl::run() block the main loop though ? Thats what I am trying to avoid as its a loop that processes lots of data.
>
> I must have missed that requirement.

Yes, the OP motioned that in nearly post... I infer that the GUI is being tagged on top of an already existing (and hard to modify) codebase...



Ian MacArthur

unread,
Oct 1, 2020, 4:31:30 PM10/1/20
to fltkg...@googlegroups.com
On 1 Oct 2020, at 19:47, lifeattickville wrote:
>
> > The "historical" model for GUI programs is the main process is the GUI handler thread, and if there is significant, potentially blocking activity, it takes place in secondary threads.
>
> Tho, thinking about it for 5 seconds, I don't see why the GUI thread couldn't be a secondary thread ...


Because, historically, the GPU context belongs only to the main thread, and historically GPU drivers were a very long way from being thread-safe.

Remember that for folks writing GPU drivers the mantra was *never* “do not be wrong” it was always “do not be slow.”

Things have got a bit better in recent years, particularly since the games houses started trying to use all those extra cores - that has somewhat forced the GPU folks to clean up their act a bit, so things *can* work like that now. On some machines. With some GPU drivers.

But generally, if you want your code to be robust and portable, don’t try and manipulate the GUI context from a non-main thread. It may not be convenient, but it less painful in the long term!

I have, more than once, had to dig folks out of a hole of their own making, for this exact issue - they had code that worked “perfectly" on their dev machine, using a non-main GUI context, and then burned in inexplicable bug-ridden fury when run on a user machine.

Maybe someday that will be fine. But not yet...

lifeatt...@gmail.com

unread,
Oct 1, 2020, 5:10:06 PM10/1/20
to fltk.general
>I think I’d just use the Fl::awake() mechanism to pass callbacks to the main thread to be processed, as that is how we envisaged doing this 

Cool! I'll have to try it. Always learning something new. I used Fl::handle_() 'cause it was the first thing I found.

lifeatt...@gmail.com

unread,
Oct 1, 2020, 5:19:42 PM10/1/20
to fltk.general
As requested, I'm attaching a sample of my message based thing. Updates the progress bar once a second.

Probably not useful to the OP, as it uses a secondary thread. Also probably not the best example, as I've not tried Ian's suggestion of Fl::awake.

Variant 1: main_gui_on_main.cpp : GUI is running on the main thread, work in secondary thread
Variant 2: main_gui_on_secondary.cpp : Work is performed on main thread, GUI is running on a secondary thread.

main_gui_on_secondary.cpp
main_gui_on_main.cpp

anmol....@gmail.com

unread,
Oct 25, 2020, 7:24:01 AM10/25/20
to fltk.general
Could Ian and Greg kindly comment on main_gui_on_secondary.cpp ? This program uses fl_create_thread and the worker thread starts the progress window and calls Fl::run().

In the docs this is forbidden.

>>
Don't call Fl::run(), Fl::wait(), Fl::flush(), Fl::check() or any related methods that will handle system messages from a worker thread
>>

Why is this working ? From my example, (no longer a progress bar but a more complex UI), I get a crash when the user closes the window if started from a worker thread.

Ian MacArthur

unread,
Oct 25, 2020, 9:13:55 AM10/25/20
to fltkg...@googlegroups.com
On 25 Oct 2020, at 11:24, anmol.mishra wrote:
>
> Could Ian and Greg kindly comment on main_gui_on_secondary.cpp ? This program uses fl_create_thread and the worker thread starts the progress window and calls Fl::run().
>
> In the docs this is forbidden.
>
> >>
> Don't call Fl::run(), Fl::wait(), Fl::flush(), Fl::check() or any related methods that will handle system messages from a worker thread
> >>
>
> Why is this working ? From my example, (no longer a progress bar but a more complex UI), I get a crash when the user closes the window if started from a worker thread.


I’d suggest that it is *not* working - though it may appear to be.

Further, I’d caution that even if it does “work" for you, it may well *not* work for other users, or even on different PC’s.
By accessing the GUI context from the non-primary thread, you are at the mercy of the GPU and it’s related OS and driver layers, and how they maintain the context; and at that point you are out into the wilds (on your own...)

The crash on exit, at a guess, is most likely caused by something unwinding the GUI context and deleting widgets and related artefacts, but since it is in the “wrong" thread context it is deleting something in the wrong memory context and segfaulting...

That’s exactly the sort of thing that will happen, and why we advise against running the GUI from any non-primary thread.

Reply all
Reply to author
Forward
0 new messages