await expression suspends indefinitely without GLib.MainContext
0 reputation · 02 Feb 2023, 13:00 UTC
0 reputation · 02 Feb 2023, 13:00 UTC
The Vala `await` expression relies on GLib's main context to dispatch the continuation callback for suspended async methods.
The threading context in which an `await` resumes is not explicitly guaranteed to match the caller's thread.
35775 reputation · 02 Feb 2023, 15:43 UTC
Guaranteed thread context: An await continuation runs on whichever thread is currently iterating the GLib.MainContext that was thread-default at the suspension point. It is not guaranteed to be the caller's original thread, nor the GTK main thread — only the thread that owns the captured context.
GTK assertion interaction: If the captured context belongs to a background thread (or no context is running), the continuation executes off the GTK main thread. Any GTK widget access in that continuation triggers gdk_threads_assert_main_thread() failures or GTK_THREAD_CHECK warnings.
await, Vala captures GLib.MainContext.get_thread_default() (the thread-default context).g_main_context_invoke() to that same context.g_main_context_iteration(), gtk_main(), GLib.MainLoop.run()).Thread.create() starts a thread; await inside captures that thread's (empty) thread-default context. No loop runs there → continuation never fires.GLib.Idle.add()/Timeout.add() with thread-default context: These schedule in the global default context, not the thread-default context. If they differ, continuations resume in the wrong place.// 1. Confirm which context is captured at the await site
assert (GLib.MainContext.get_thread_default() == GLib.MainContext.default());
// If this fails, you've identified the mismatch.
// 2. Prove the main loop is actually iterating
GLib.Idle.add(() => {
stdout.printf("main loop alive\n");
return GLib.SOURCE_REMOVE;
});
// 3. Minimal reproducer: does a simple timeout await resume?
async void test() {
await GLib.Timeout.add(100);
stdout.printf("resumed\n");
}
// Call test() from your startup path. If "resumed" never prints, the captured context has no running loop.
GLib.Idle.add(), GLib.Timeout.add(), or Gtk.Application::activate — all run on the main thread with the main context as thread-default.var main_ctx = GLib.MainContext.default();
main_ctx.push_thread_default();
try {
await some_async_operation();
} finally {
main_ctx.pop_thread_default();
}
GLib.MainLoop to the thread and run it, or marshal back to the main context via GLib.Idle.add() before awaiting.Does the problematic await occur inside a callback from a third-party library (libsoup, gstreamer, dbus, etc.) or a manually created Thread? This determines whether the fix is "push main context before call" or "restructure entry point."
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.