参考:
GLib中的GTask代码示例解读_ofono里的glib代码-CSDN博客
概述
GTask定义在gio这个库中,gio虽然和glib是两个不同的库,但从概念来说,它们都属于glib下面的内容,它们都是由GLib开发组提供的。
按照GIO手册的定义:“Gio是一个库,为通用I/O、网络、IPC、设置和其他高级应用程序功能提供有用的类”。
GTask表示并管理一个可取消的“任务”,常用于“异步操作”中,本文即重点介绍一下GTask在异步操作中的应用,从而通过这个应用去理解GTask。
异步操作的基本模式
与同步操作不一样,异步操作一般会提供两个接口作为一组。其中一个接口启动功能,另一个接口则是功能的结束(一般称为finish函数)。例如手册中给出的例子中,这样一组接口即为:
- baker_bake_cake_async
- baker_bake_cake_finish
为了实现异步,不得不提到回调函数。设想一下,作为调用者,如果简单地先调用baker_bake_cake_async,然后再baker_bake_cake_finish,其实没有实现真正的异步操作。
{
baker_bake_cake_async();
...
baker_bake_cake_finish();
}
这是因为,调用者在调用finish函数的时候,异步操作可能还在执行中,此时在调用finish函数的时候,程序必然会等在这里,也就是finish函数被“过早”地调用了。反之,则可能存在finish函数被“过晚”地调用。无论过早或者过晚,作为调用者,都不太合适。为了解决这个问题,就会用到回调函数。程序示意如下:
my_callback()
{
baker_bake_cake_finish();
...
}
{
baker_bake_cake_async(my_callback);
...
}
即调用者把功能执行完后所需的处理封装在一个my_callback()的回调函数中,并且在调用baker_bake_cake_async()的时候,注入这个回调函数。这样,当异步操作执行完后,自动触发回调函数的执行,再由这个回调函数调用finish函数获取异步操作的执行结果。
最后,需要提一下程序运行的线程。对线程熟悉的同学可能已经发现了,在前面回调函数的异步操作模式中,回调函数往往是在底层的任务执行的线程中被触发的,因此回调函数往往会在底层任务执行的线程中运行,而不是调用者的线程。这样的做法并不好,会造成调用者的代码运行在两个不同的线程中,因为回调函数也是由调用者书写。而使用gtask创建的异步操作,会解决这一问题,即由gtask创建的异步操作、其回调函数也会在调用者的线程中运行。
使用gtask实现异步操作
“GTask最常见的用法是作为GAsyncResult,在异步操作期间管理数据”。
通常的用法如下:
- 使用g_task_new()创建一个gtask
- 如果需要在task的执行过程中保存一些数据信息,可以使用g_task_set_task_data()
- task执行完后,需要显示返回。正常返回时使用g_task_return_pointer(),错误返回时使用g_task_return_error()。注意:一旦返回,则gtask会自动调用创建task时设置的回调函数。并且,这个回调函数是在创建task的线程中执行的(即回到了原先的线程)
- 在回调函数中,通常会调用异步模式下的finish函数。而在finish函数中,可以通过调用g_task_propagate_pointer将前面g_task_return_xxx()的信息返回给异步接口的使用者。
- 由于异步操作的回调函数会回到原来创建task的线程中,因此整个异步操作需要运行在GMainLoop这个线程循环中。调用g_main_loop_new()和g_main_loop_run()来实现这个功能。
简单解释一下为什么回调函数会回到原来的线程中运行。glib对线程运行的环境定义了GMainContext,这保存了一些线程的信息。gtask在执行回调函数的时候,会调用g_main_context_push_thread_default()将这个回调函数放到默认线程(即调用者的线程)中,而这个默认线程会通过自身GMainLoop的“下一次迭代”来处理这个回调函数。也就是说,回调函数实际上是被默认线程自身的main loop调用的,所以回调函数运行在原来的默认线程中。这也是为什么执行gtask异步操作之前,需要g_main_loop_new()和g_main_loop_run()的原因。
下面看完整的程序。由于手册中的程序并不完整,缺少了“调用者”的代码,所以这里借用了GLib中的GTask代码示例解读_ofono里的glib代码-CSDN博客中给出的代码。示例代码实际上是基于手册中的“Asynchronous operations from synchronous ones”中的程序
#include <glib.h>
#include <glib-object.h>
#include <gio/gio.h>
typedef int CakeFlavor;
typedef int CakeFrostingType;
typedef GObject Baker; // task object
typedef GObject Cake; // output object of the task
typedef struct
{
guint radius;
CakeFlavor flavor;
CakeFrostingType frosting;
char *message;
} CakeData; // task data
static void cake_data_free(CakeData *cake_date)
{
g_print("%p: %s\n", g_thread_self(), __func__);
if (NULL == cake_date)
{
return;
}
if (NULL != cake_date->message)
{
g_free(cake_date->message);
cake_date->message = NULL;
}
g_slice_free(CakeData, cake_date);
}
static Cake *bake_cake(Baker *self,
guint radius,
CakeFlavor flavor,
CakeFrostingType frosting,
char *message,
GCancellable *cancellable,
GError **error)
{
g_print("%p: %s\n", g_thread_self(), __func__);
return g_object_new(G_TYPE_OBJECT, NULL);
}
static void bake_cake_thread_func(GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
g_assert(NULL != source_object);
g_assert(NULL != task_data);
Baker *self = source_object;
CakeData *cake_data = task_data;
Cake *cake = NULL;
GError *error = NULL;
g_print("%p: %s\n", g_thread_self(), __func__);
cake = bake_cake(self,
cake_data->radius,
cake_data->flavor,
cake_data->frosting,
cake_data->message,
cancellable,
&error);
if (cake)
g_task_return_pointer(task, cake, (GDestroyNotify)g_object_unref);
else
g_task_return_error(task, error);
}
static void baker_bake_cake_async(Baker *self,
guint radius,
CakeFlavor flavor,
CakeFrostingType frosting,
char *message,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
CakeData *cake_data = NULL;
GTask *task = NULL;
g_print("%p: %s\n", g_thread_self(), __func__);
cake_data = g_slice_new0(CakeData);
cake_data->radius = radius;
cake_data->flavor = flavor;
cake_data->frosting = frosting;
cake_data->message = g_strdup(message);
task = g_task_new(self, cancellable, callback, user_data);
g_task_set_task_data(task, cake_data, (GDestroyNotify)cake_data_free);
g_task_run_in_thread(task, bake_cake_thread_func);
g_object_unref(task);
}
static Cake *baker_bake_cake_finish(Baker *self,
GAsyncResult *res,
GError **error)
{
g_return_val_if_fail(g_task_is_valid(res, self), NULL);
g_print("%p: %s\n", g_thread_self(), __func__);
return g_task_propagate_pointer(G_TASK(res), error);
}
static void my_callback(GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
g_print("%p: %s\n", g_thread_self(), __func__);
Baker *baker = (Baker *)source_object;
GMainLoop *loop = (GMainLoop *)user_data;
Cake *cake = NULL;
GError *error = NULL;
cake = baker_bake_cake_finish(baker, res, &error);
g_print("A cake is baked: %s\n", __func__);
g_object_unref(cake);
g_main_loop_quit(loop);
}
int main(void)
{
Baker *baker = g_object_new(G_TYPE_OBJECT, NULL);
GCancellable *cancellable = g_cancellable_new();
GMainLoop *loop = g_main_loop_new(NULL, FALSE);
baker_bake_cake_async(baker, 10, 20, 30, "emit", cancellable, my_callback, loop);
g_object_unref(cancellable);
g_print("%p start event loop.\n", g_thread_self());
g_main_loop_run(loop);
g_object_unref(baker);
return 0;
}
程序说明:
- baker_bake_cake_async和baker_bake_cake_finish是使用gtask实现的异步接口;main和my_callback是使用这组异步接口的上层应用
- 该组异步接口(在形式上)实现的功能是:使用给定的“baker”,去“bake”一个“cake”。之所以说是形式上的,是因为实际上在“bake”一个“cake”的过程中,程序并没有真正用到“baker”对象(见函数bake_cake)。
- 在接口baker_bake_cake_async中,我们可以调用了g_task_run_in_thread(task, bake_cake_thread_func),这样做的目的是让底层task在另一个线程中运行,而不是在main函数所在的主线程中
- GMainLoop的用法:main函数在调用了g_main_loop_run()之后,main函数的执行就停在这一行了,并不会继续执行后面的g_object_unref(baker),也就是形成了主线程。只有当执行到g_main_loop_quit()之后,才会执行main函数中g_main_loop_run()之后的代码。程序中,g_main_loop_quit()是在my_callback()中调用的,也就是在异步操作finish之后。
- 程序在很多地方都使用g_thread_self()打印了线程地址,这是为了查看每段代码是在哪个线程中运行的。
程序输出如下:
0x55af5295f630: baker_bake_cake_async
0x55af5295f630 start event loop.
0x55af52961850: bake_cake_thread_func
0x55af52961850: bake_cake
0x55af5295f630: my_callback
0x55af5295f630: baker_bake_cake_finish
A cake is baked: my_callback
0x55af5295f630: cake_data_free
可以看到,主线程地址为0x55af5295f630,底层task(bake cake)在另一个线程0x55af52961850中。最后回调函数my_callback回到了主线程。
附Makefile
gtask位于libgio这个库中,所以需要增加"lgio-2.0"这个库
.PHONY: clean
CC = gcc
RM = rm
EXE = test_gtask
CFLAGS=-pthread -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include
LDFLAGS=-lgio-2.0 -lgobject-2.0 -lglib-2.0
SRCS = $(wildcard *.c)
OBJS = $(patsubst %.c,%.o,$(SRCS))
$(EXE):$(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
%.o: %.c
$(CC) $(CFLAGS) -o $@ -c $^
clean:
$(RM) $(EXE) $(OBJS)