GTK background threads

Got a request recently for that topic. Generally I try to ignore such requests, as such people often vanish fast. But it seems that he is serious interest and indeed intents to use GTK.

As GTK is not really thread safe and does not support threading well, we generally use

https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#g-timeout-add

to receive data from other threads and to display that data in GTK.

I send him an example for that yesterday. We create a new worker thread, and send the data over Channels to the function which is periodically called by g-timeout-add(). Seems to work fine but may be a bit ugly, as it looks like polling for data:

We found a gtkmm example which seems to be more advanced and seems to do not need g-timeout-add():

https://developer.gnome.org/gtkmm-tutorial/unstable/sec-multithread-example.html.en

Seems to be already GTK4. Would that program shape work also in plain C, or is special C++ support included? Unfortunately I do not know much about advanced C++, a timeout function seems to be not included, so it looks cleaner.

Do we have good GTK threading examples for other languages, like Go, D, Rust?

EDIT: I forget to mention that I know about

https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#g-idle-add

We should be able use g-idle-add() in the worker thread to update the GTK GUI. Seems to be also not really elegant, but should work. Maybe I will try to provide a Nim example in the next days.

#include <thread>
#include <mutex>

That’s coming from C++, not GLib/Gtk

You probably want to look at GAsyncQueue / GTask as the main tools for threading with GLib, no idea how that works in nim-land though

The example avoids using an explicit timeout because it’s using glibmm’s Dispatcher class:

From the glibmm documentation:

But unlike normal signals, the notification happens asynchronously through a pipe. This is a simple and efficient way of communicating between threads, and especially useful in a thread model with a single GUI thread.

I believe glibmm is essentially wrapping g_io_create_watch() and friends here, so it should be possible to achieve the same result using those, although I’ve never tried that personally.

1 Like

@zbrown’s answer is probably what you want (but for the Nim integration that I don’t know any better than he does).

However, a couple things:

The gtkmm example you linked is using Glib::Dispatcher from glibmm (it’s not a GLib thing, it’s a glibmm one). Apparently it’s a fancy pipe, basically allows to call a callback in another thread.
It seems mostly similar to using g_idle_add() in the thread to call a callback in the GLib main context.

However, what you show doing really is polling, and it’s not what is usually intended when suggesting to use idle callbacks for communicating with the GLib main thread: you are supposed to call g_idle_add() from the thread as a mean to call the a callback in the main context. Not have a timeout and periodically ask whether there’s data.

All this said, it looks like Nim has a a kind of pipe for communication here. What would seem best to me would be to integrate this thing into the GLib main source so it is possible to monitor such a channel and know when it has data to be read, and react upon that. For a plain pipe (or anything else than can use a GIOChannel), you could have been using g_io_add_watch(). Maybe you could use something similar for Nim channels.

Thanks for all your comments, I will read them carefully.

The preferred way to perform work in a thread is to use GTask. Once the task is complete, you can update the UI, as the callback used when constructing a new GTask instance is guaranteed to be invoked in the same context as the one that created the task, which means the same context used by GTK. Additionally GTask guarantees that the callback will be invoked on error and cancellation.

Any other homegrown solution must ensure the same guarantees, so you might as well use the API provided to you by GIO.

1 Like

Thanks, I will read more about GTask later.

Have just created a solution with

https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#g-idle-add

that one is simple and seems to work also:

# nim c --threads:on --gc:arc -r t.nim
import gintro/[gtk, glib, gobject, gio]
from  os import sleep

var worker: system.Thread[void]
var button: Button

proc idleFunc(i: int): bool =
  button.label = $i
  return SOURCE_REMOVE

proc work() =
  var countdown {.global.} = 25
  while countdown > 0:
    sleep(1000)
    dec(countdown)
    idleAdd(idleFunc, countdown)

proc buttonClicked (button: Button) =
  button.label = utf8Strreverse(button.label, -1)

proc appActivate (app: Application) =
  let window = newApplicationWindow(app)
  window.title = "Countdown"
  window.defaultSize = (250, 50)
  button = newButton("Click Me")
  window.add(button)
  button.connect("clicked",  buttonClicked)
  window.showAll
  createThread(worker, work)

proc main =
  let app = newApplication("org.gtk.example")
  connect(app, "activate", appActivate)
  discard app.run

main()

You can also use g_main_context_invoke() with the default GMainContext, instead of g_idle_add().

2 Likes

Have just a short look at GTask. It is not easy to understand all.

First question: Does GTask execute threads really in parallel, that is that multiple CPU cores are used on a multicore maschine?

And can we update the GTK GUI from within a thread started by GTask?

I have the feeling that both answers are no.

A possible example would be a chess engine: GTask may be fine to launch a thread, and and to receive the result. Maybe even to cancel the thread when time limit is reached. But running really in parallel would be nice. And permanently updating a Widget with the currently best found move would be desired.

GTask maintains a thread pool that is based on the number of CPUs available. So, if you launch multiple GTask instances, you’ll get multiple threads up to the point where you hit all the CPU cores you have.

Not from the function you pass to g_task_run_in_thread(): that function is executed in a thread. The callback you pass to g_task_new(), on the other hand, is executed in the same main context that created the GTask instance, which means you can update your UI from there.

Yes, that’s how GTask is typically used. Or to download a large file from the network, for instance.

If you wish to update the UI from the thread function, you can call g_main_context_invoke() from there; this is how functions like g_file_copy_async() are implemented: the copy runs asynchronously in a thread, and your progress callback gets invoked while the operation is in progress; then you get the result at the end of the operation.

2 Likes

Thanks for your reply.

GTask maintains a thread pool that is based on the number of CPUs available. So, if you launch multiple GTask instances, you’ll get multiple threads up to the point where you hit all the CPU cores you have.

That important info really belongs to GTask API – at the top. I have not seen it at all there, but I have not read carefully yet.

It’s an implementation detail; it does not belong in the API reference. It could be a threading pool based on the phase of the Moon and the position of Saturn, for all people care.

The point is that if you call g_task_run_in_thread(), you’ll get a blocking operation executed in a separate thread. The number of threads available to you are inconsequential: you could call it in a loop for 10000 times (something our test suite does), and you wouldn’t ever notice.

I was reffering to the fact that the threads are executed in parallel for GTask. Async and threads does not always imply parallel execution on multiple CPU cores. For IO-bound task it is ok when all threads run on a single CPU, but for CPU intensive task like a chess engine or scientific number crunching it is not. And there exists async implementations that does only run on a single CPU-core. So it is good and important that GTask supports multiple CPU-cores.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.