yield in vala and gio concurrency

By no means, I’m an expert in vala or gio. Infact, I’m just a beginner, but I have learned something while playing with vala which I want to record (for future reference). Here I’ll explain how Gio’s async framework works and how vala uses Gio beneath the tree.

Gio Async Framework:

Better to explain with an example,

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include <glib-object.h>
#include <gio/gio.h>
#define _g_object_unref0(var) ((var == NULL) ? NULL : (var = (g_object_unref (var), NULL)))
#define TEST_TYPE_GIO_ASYNC (test_gio_async_get_type())
#define TEST_GIO_ASYNC(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TEST_TYPE_GIO_ASYNC, TestGioAsync))
#define TEST_GIO_ASYNC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), TEST_TYPE_GIO_ASYNC, TestGioAsyncClass))
#define TEST_IS_GIO_ASYNC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TEST_TYPE_GIO_ASYNC))
#define TEST_IS_GIO_ASYNC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), TEST_TYPE_GIO_ASYNC))
#define TEST_GIO_ASYNC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), TEST_TYPE_GIO_ASYNC, TestGioAsyncClass))
#define TEST_GIO_ASYNC_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), TEST_TYPE_GIO_ASYNC, TestGioAsyncPrivate))
typedef struct _TestGioAsync TestGioAsync;
typedef struct _TestGioAsyncClass TestGioAsyncClass;
typedef struct _TestGioAsyncPrivate TestGioAsyncPrivate;
struct _TestGioAsyncClass {
GObjectClass parent;
};
struct _TestGioAsyncPrivate {
gchar *something_to_say;
};
struct _TestGioAsync {
GObject parent_instance;
TestGioAsyncPrivate *priv;
};
enum {
TEST_GIO_ASYNC_DUMMY_PROPERTY,
TEST_GIO_ASYNC_SOMETHING_TO_SAY
};
static gpointer test_gio_async_parent_class = NULL;
GType test_gio_async_get_type(void) G_GNUC_CONST;
static void test_gio_async_set_property(GObject *obj, guint prop_id, const GValue *value, GParamSpec *spec) {
TestGioAsyncPrivate *priv = NULL;
priv = TEST_GIO_ASYNC_GET_PRIVATE(obj);
switch(prop_id) {
case TEST_GIO_ASYNC_SOMETHING_TO_SAY:
priv->something_to_say = g_value_dup_string(value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
}
}
static void test_gio_async_get_property(GObject *obj, guint prop_id, GValue *value, GParamSpec *spec) {
TestGioAsyncPrivate *priv = NULL;
priv = TEST_GIO_ASYNC_GET_PRIVATE(obj);
switch(prop_id) {
case TEST_GIO_ASYNC_SOMETHING_TO_SAY:
g_value_set_string(value, priv->something_to_say);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
}
}
static void test_gio_async_instance_init(TestGioAsync *self) {
self->priv = TEST_GIO_ASYNC_GET_PRIVATE(self);
}
static void test_gio_async_finalize(GObject *obj) {
TestGioAsyncPrivate *priv = NULL;
priv = TEST_GIO_ASYNC_GET_PRIVATE(obj);
g_free(priv->something_to_say);
G_OBJECT_CLASS(test_gio_async_parent_class)->finalize(obj);
}
static void test_gio_async_class_init(TestGioAsyncClass *klass) {
test_gio_async_parent_class = g_type_class_peek_parent(klass);
g_type_class_add_private(klass, sizeof(TestGioAsyncPrivate));
G_OBJECT_CLASS(klass)->set_property = test_gio_async_set_property;
G_OBJECT_CLASS(klass)->get_property = test_gio_async_get_property;
G_OBJECT_CLASS(klass)->finalize = test_gio_async_finalize;
g_object_class_install_property(G_OBJECT_CLASS(klass),
TEST_GIO_ASYNC_SOMETHING_TO_SAY,
g_param_spec_string("something_to_say",
"something_to_say",
"something_to_say",
NULL,
G_PARAM_READABLE |
G_PARAM_WRITABLE |
G_PARAM_CONSTRUCT));
}
GType test_gio_async_get_type(void) {
static volatile gsize test_gio_async_type_id__volatile = 0;
if(g_once_init_enter(&test_gio_async_type_id__volatile)) {
static const GTypeInfo g_define_type_info = { sizeof(TestGioAsyncClass),
(GBaseInitFunc) NULL,
(GBaseFinalizeFunc) NULL,
(GClassInitFunc) test_gio_async_class_init,
(GClassFinalizeFunc) NULL,
NULL,
sizeof(TestGioAsync),
0,
(GInstanceInitFunc) test_gio_async_instance_init,
NULL };
, GType test_gio_async_type_id = g_type_register_static(G_TYPE_OBJECT,
"TestGioAsync",
&g_define_type_info,
0);
g_once_init_leave(&test_gio_async_type_id__volatile, test_gio_async_type_id);
}
return test_gio_async_type_id__volatile;
}
TestGioAsync* test_gio_async_new(void) {
return g_object_new(TEST_TYPE_GIO_ASYNC, NULL);
}
static gboolean test_gio_async_say_idle_func(gpointer user_data) {
TestGioAsync *self = (TestGioAsync *) user_data;
GSimpleAsyncResult *simple = g_object_get_data(G_OBJECT(self), "simple");
gchar *result = NULL;
g_object_get(G_OBJECT(self), "something_to_say", &result, NULL);
g_simple_async_result_set_op_res_gpointer(simple, result, g_free);
g_simple_async_result_complete_in_idle(simple);
_g_object_unref0(simple);
return FALSE;
}
void test_gio_async_say(TestGioAsync *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data) {
GSimpleAsyncResult *simple = g_simple_async_result_new(G_OBJECT(self),
callback,
user_data,
test_gio_async_say);
g_simple_async_result_set_check_cancellable(simple, cancellable);
g_object_set_data(G_OBJECT(self), "simple", simple);
g_idle_add(test_gio_async_say_idle_func, self);
}
gchar* test_gio_async_say_finish(TestGioAsync *self,
GAsyncResult *res,
GError **error) {
GSimpleAsyncResult *simple = NULL;
g_return_val_if_fail(g_simple_async_result_is_valid(res, G_OBJECT(self), test_gio_async_say), NULL);
simple = (GSimpleAsyncResult *) res;
if(error) g_simple_async_result_propagate_error(simple, error);
return g_simple_async_result_get_op_res_gpointer(simple);
}
static void test_gio_async_say_cb(GObject *self, GAsyncResult *res, gpointer user_data) {
gchar *result = test_gio_async_say_finish(TEST_GIO_ASYNC(self), res, NULL);
GMainLoop *mainloop = (GMainLoop *) user_data;
g_print("%s\n", result);
g_main_loop_quit(mainloop);
}
int main(int argc, char *argv[]) {
TestGioAsync *test = test_gio_async_new();
GMainLoop *mainloop = g_main_loop_new(NULL, TRUE);
g_object_set(G_OBJECT(test), "something_to_say", "hello world", NULL);
test_gio_async_say(test, NULL, test_gio_async_say_cb, mainloop);
g_print("started saying..\n");
g_main_loop_run(mainloop);
return 0;
}

view raw
testgioasync.c
hosted with ❤ by GitHub

Here, I have created a new GObject class called TestGioAsync with something_to_say property. I have implemented a simple async function called test_gio_async_say.  When called, test_gio_async_say will setup GSimpleAsync object, attach another function called test_gio_async_say_idle_func into GMainLoop’s next idle iteration and return.

Once test_gio_async_say_idle_func triggered, it takes the value from something_to_say property and put it into g_simple_async_result_set_op_res_pointer as op_res (which will be picked-up later) and completes the async transaction with g_simple_async_result_complete_in_idle which will call the test_gio_async_say_cb provided in test_gio_async_say in GMainLoop’s next idle iteration.

Once test_gio_async_say_cb triggered, it will call test_gio_async_say_finish which will simply return op_res which contains the value taken from something_to_say property.

Vala Async Framework:

This is the equivalent vala example for the above C example

#!/usr/bin/env vala
class Test.Async : GLib.Object {
public async string say(string sentence) {
GLib.Idle.add(this.say.callback);
yield;
return sentence;
}
public static int main(string[] args) {
Test.Async myasync = new Test.Async();
GLib.MainLoop mainloop = new GLib.MainLoop();
myasync.say.begin("helloworld",
(obj, res) => {
string sentence = myasync.say.end(res);
print("%s\n", sentence);
mainloop.quit();
});
mainloop.run();
return 0;
}
}

view raw
testasync.vala
hosted with ❤ by GitHub

Vala async functions returns immediately whenever it reaches yield keyword, In this example, I have hooked this.say.callback into GMainLoop’s next idle iteration. In the next idle iteration, this.say will be called and will start execution exactly after the yield statement which will eventually trigger the anonymous lambda function provided in  this.say.begin with sentence as return value.

Once the lambda function triggered, it will call this.say.end which will return the sentence value to the lambda function.

In Vala example, this.say.callback is equivalent to test_gio_async_say_idle_func in C example.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s