GTask简介

参考:

Gio.Task

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)

  • 19
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值