Home > Software engineering >  What are the patterns for performing sequential work with GTK-style async APIs in C
What are the patterns for performing sequential work with GTK-style async APIs in C

Time:10-16

The APIs I'm working with right now (libnm specifically) have many of the synchronous calls labeled as deprecated with the asynchronous calls the preferred way to use the API. These are 'gtk-style asynchronous methods which take the form of operation_async( <args>, <callback fptr> ) with a callback then processing operation_finish() and I guess updating some (global?!) system state.

What are the preferred patterns for writing readable code when using an API like this in C?

Do I just ignore the deprecated tags and use the synchronous calls (preferably in a worker thread)?

Do I make a string of 'breadcrumb' callbacks which each call operation_n 1_async( <args>, <callback n 1)?

Do I make manually synchronize to a series of callbacks using a mutex or similar for each so that I can keep most of the code in one worker function (probably in it's own thread) written in sequential order and just block it until the dependent operations complete?

edit: Further reading from Gnome documentation on "main contexts" suggests maybe starting a worker thread which has it's own main loop (or copy of the main loop? not sure on my read). The callbacks can then quit the main loop when the last one terminates, each returning their data to the worker thread. For further stages the worker thread can simply restart the main loop with the state it has in the stack and new _async() calls.

Is there some other pattern entirely?

And does GLIB/GIO provide some macros or pre-built callbacks to make one of these patterns simpler to run.

CodePudding user response:

According to the Gnome tutorial on asynchronous programming the first option is definitely one of the strategies, with a twist:

If you prototype the callback functions you can then write them in order after the function with the first call. This turns them from a seemingly-unrelated set of functions that takes effort to connect into a 'single' long procedure which is broken up into several coroutines. There's more boilerplate than I'd like but it meets the goals of being readable and relatively easy to implement.

With heavy paraphrasing and simplification this example is something like this:

static void my_operation_cb1 (GObject *source_object, GAsyncResult *result, gpointer user_data);
static void my_operation_cb2 (GObject *source_object, GAsyncResult *result, gpointer user_data);
static void my_operation_cb3 (GObject *source_object, GAsyncResult *result, gpointer user_data);

static void my_operation (MyObject *self)
{
  <perform setup>
  api_operation1_async (<arg>, <cancellable>, my_operation_cb1, self);
}

static void my_operation_cb1 (GObject *source_object, GAsyncResult *result, gpointer user_data)
{
  rc = api_operation1_finish (<arg>, result, NULL, &error);

  processed_result=process_1(result);

  g_socket_client_connect_async (processed_result, <cancellable>, my_operation_cb2, self);
}

static void my_operation_cb2 (GObject *source_object, GAsyncResult *result, gpointer user_data)
{
  rc = api_operation1_finish (<arg>, result, NULL, &error);

  processed_result=process2(result);

  g_socket_client_connect_async (processed_result, <cancellable>, my_operation_cb3, self);
}

static void my_operation_cb3 (GObject *source_object, GAsyncResult *result, gpointer user_data)
{
  rc = api_operation1_finish (<arg>, result, NULL, &error);

  overall_result = process3(result);

}

CodePudding user response:

Regarding updating system state, it doesn't need to be global - you pass it from one callback to another using the user_data parameter.

For example:

struct CallbackData {
  MyObject *self;
  int field;
  ...
}
static void my_operation (MyObject *self) {
  struct CallbackData *data = g_new0(struct CallbackData, 1);
  data->self = self;
  data->field = 42;
  operation_async(..., (GAsyncReadyCallback)callback1, data);
}
static void callback1 (GObject *source, GAsyncResult *res, struct CallbackData *data) {
  ...
}
  • Related