A technique that we started using in Tracker is utilizing the mainloop to do asynchronous functions. We decided that avoiding threads is often not a bad idea.
Instead of instantly falling back to throwing work to a worker thread we try to encapsulate the work into a GSource’s callback, then we let the callback happen until all of the work is done.
An example
You probably know sqlite3’s backup API? If not, it’s fairly simple: you do sqlite3_backup_init, followed by a bunch of sqlite3_backup_step calls, finalizing with sqlite3_backup_finish. How does that work if we don’t want to block the mainloop?
I removed all error handling for keeping the code snippet short. If you want that you can take a look at the original code.
static gboolean backup_file_step (gpointer user_data) { BackupInfo *info = user_data; int i; for (i = 0; i < 100; i++) { if ((info->result = sqlite_backup_step(info->backup_db, 5)) != SQLITE_OK) return FALSE; } return TRUE; } static void backup_file_finished (gpointer user_data) { BackupInfo *info = user_data; GError *error = NULL; if (info->result != SQLITE_DONE) { g_set_error (&error, _DB_BACKUP_ERROR, DB_BACKUP_ERROR_UNKNOWN, "%s", sqlite3_errmsg ( info->backup_db)); } if (info->finished) info->finished (error, info->user_data); if (info->destroy) info->destroy (info->user_data); g_clear_error (&error); sqlite3_backup_finish (info->backup); sqlite3_close (info->db); sqlite3_close (info->backup_db); g_free (info); } void my_function_make_backup (const gchar *dbf, OnBackupFinished finished, gpointer user_data, GDestroyNotify destroy) { BackupInfo *info = g_new0(BackupInfo, 1); info->user_data = user_data; info->destroy = destroy; info->finished = finished; info->db = db; sqlite3_open_v2 (dbf, &info->db, SQLITE_OPEN_READONLY, NULL); sqlite3_open ("/tmp/backup.db", &info>backup_db); info->backup = sqlite3_backup_init (info->backup_db, "main", info->db, "main"); g_idle_add_full (G_PRIORITY_DEFAULT, backup_file_step, info, backup_file_finished); }
Note that I’m not suggesting to throw away all your threads and GThreadPool uses now.
Note that just like with threads you have to be careful about shared data: this way you’ll allow that other events on the mainloop will interleave your backup procedure. This is async(ish), it’s precisely what you want, of course.