[glib] GTask: new GAsyncResult implementation / threaded task manager



commit 4aeefa70a10b89b53a3bd1ed305d451f3cc6d2ad
Author: Dan Winship <danw gnome org>
Date:   Wed Oct 5 10:05:50 2011 -0400

    GTask: new GAsyncResult implementation / threaded task manager
    
    GTask is a replacement for GSimpleAsyncResult and GIOScheduler, that
    also allows for making cancellable wrappers around non-cancellable
    functions (as in GThreadedResolver).
    
    https://bugzilla.gnome.org/show_bug.cgi?id=661767

 docs/reference/gio/gio-docs.xml     |    1 +
 docs/reference/gio/gio-sections.txt |   53 +
 docs/reference/gio/gio.types        |    1 +
 gio/Makefile.am                     |    2 +
 gio/gio.h                           |    1 +
 gio/gio.symbols                     |   31 +
 gio/giotypes.h                      |    1 +
 gio/gtask.c                         | 1805 +++++++++++++++++++++++++++++++++++
 gio/gtask.h                         |  160 +++
 gio/tests/.gitignore                |    1 +
 gio/tests/Makefile.am               |    4 +
 gio/tests/task.c                    | 1668 ++++++++++++++++++++++++++++++++
 12 files changed, 3728 insertions(+), 0 deletions(-)
---
diff --git a/docs/reference/gio/gio-docs.xml b/docs/reference/gio/gio-docs.xml
index f5c599d..f1ad8a3 100644
--- a/docs/reference/gio/gio-docs.xml
+++ b/docs/reference/gio/gio-docs.xml
@@ -42,6 +42,7 @@
         <xi:include href="xml/gasyncresult.xml"/>
         <xi:include href="xml/gioscheduler.xml"/>
         <xi:include href="xml/gsimpleasyncresult.xml"/>
+        <xi:include href="xml/gtask.xml"/>
     </chapter>
     <chapter id="conversion">
         <title>Data conversion</title>
diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt
index c9dd506..a3a0fb0 100644
--- a/docs/reference/gio/gio-sections.txt
+++ b/docs/reference/gio/gio-sections.txt
@@ -3921,3 +3921,56 @@ g_test_dbus_unset
 g_test_dbus_get_type
 g_test_dbus_flags_get_type
 </SECTION>
+
+<SECTION>
+<FILE>gtask</FILE>
+<TITLE>GTask</TITLE>
+GTask
+g_task_new
+g_task_set_task_data
+g_task_set_priority
+g_task_set_check_cancellable
+g_task_set_return_on_cancel
+g_task_set_source_tag
+<SUBSECTION>
+g_task_report_error
+g_task_report_new_error
+<SUBSECTION>
+g_task_get_task_data
+g_task_get_priority
+g_task_get_cancellable
+g_task_get_check_cancellable
+g_task_get_return_on_cancel
+g_task_get_context
+g_task_get_source_object
+g_task_get_source_tag
+<SUBSECTION>
+g_task_return_boolean
+g_task_return_int
+g_task_return_pointer
+g_task_return_error
+g_task_return_new_error
+g_task_return_error_if_cancelled
+<SUBSECTION>
+g_task_propagate_boolean
+g_task_propagate_int
+g_task_propagate_pointer
+g_task_had_error
+<SUBSECTION>
+g_task_run_in_thread
+g_task_run_in_thread_sync
+GTaskThreadFunc
+g_task_attach_source
+<SUBSECTION>
+g_task_is_valid
+<SUBSECTION Standard>
+GTaskClass
+GTaskPrivate
+G_TYPE_TASK
+G_TASK
+G_IS_TASK
+G_TASK_CLASS
+G_IS_TASK_CLASS
+G_TASK_GET_CLASS
+g_task_get_type
+</SECTION>
diff --git a/docs/reference/gio/gio.types b/docs/reference/gio/gio.types
index 9eec8a9..5cd996c 100644
--- a/docs/reference/gio/gio.types
+++ b/docs/reference/gio/gio.types
@@ -135,3 +135,4 @@ g_menu_get_type
 g_menu_item_get_type
 g_test_dbus_get_type
 g_test_dbus_flags_get_type
+g_task_get_type
diff --git a/gio/Makefile.am b/gio/Makefile.am
index 20bcd81..cbafbf6 100644
--- a/gio/Makefile.am
+++ b/gio/Makefile.am
@@ -430,6 +430,7 @@ libgio_2_0_la_SOURCES =		\
 	gproxyaddressenumerator.c \
 	gsocketservice.c	\
 	gsrvtarget.c		\
+	gtask.c			\
 	gtcpconnection.c	\
 	gtcpwrapperconnection.c \
 	gthreadedsocketservice.c\
@@ -599,6 +600,7 @@ gio_headers =			\
 	gsocketlistener.h	\
 	gsocketservice.h	\
 	gsrvtarget.h		\
+	gtask.h			\
 	gtcpconnection.h	\
 	gtcpwrapperconnection.h \
 	gthreadedsocketservice.h\
diff --git a/gio/gio.h b/gio/gio.h
index 3fb914d..1998a64 100644
--- a/gio/gio.h
+++ b/gio/gio.h
@@ -122,6 +122,7 @@
 #include <gio/gsocketlistener.h>
 #include <gio/gsocketservice.h>
 #include <gio/gsrvtarget.h>
+#include <gio/gtask.h>
 #include <gio/gtcpconnection.h>
 #include <gio/gtcpwrapperconnection.h>
 #include <gio/gtestdbus.h>
diff --git a/gio/gio.symbols b/gio/gio.symbols
index ab207fa..b2a95b2 100644
--- a/gio/gio.symbols
+++ b/gio/gio.symbols
@@ -1767,3 +1767,34 @@ g_test_dbus_down
 g_test_dbus_stop
 g_test_dbus_up
 g_test_dbus_unset
+g_task_attach_source
+g_task_get_cancellable
+g_task_get_check_cancellable
+g_task_get_context
+g_task_get_priority
+g_task_get_return_on_cancel
+g_task_get_source_object
+g_task_get_source_tag
+g_task_get_task_data
+g_task_get_type
+g_task_had_error
+g_task_is_valid
+g_task_new
+g_task_propagate_boolean
+g_task_propagate_int
+g_task_propagate_pointer
+g_task_run_in_thread
+g_task_run_in_thread_sync
+g_task_set_check_cancellable
+g_task_set_priority
+g_task_set_return_on_cancel
+g_task_set_source_tag
+g_task_set_task_data
+g_task_report_error
+g_task_report_new_error
+g_task_return_boolean
+g_task_return_error
+g_task_return_error_if_cancelled
+g_task_return_int
+g_task_return_new_error
+g_task_return_pointer
diff --git a/gio/giotypes.h b/gio/giotypes.h
index 876d856..82845c5 100644
--- a/gio/giotypes.h
+++ b/gio/giotypes.h
@@ -199,6 +199,7 @@ typedef struct _GSocketAddress                GSocketAddress;
 typedef struct _GSocketAddressEnumerator      GSocketAddressEnumerator;
 typedef struct _GSocketConnectable            GSocketConnectable;
 typedef struct _GSrvTarget                    GSrvTarget;
+typedef struct _GTask                         GTask;
 /**
  * GTcpConnection:
  *
diff --git a/gio/gtask.c b/gio/gtask.c
new file mode 100644
index 0000000..48ddf6a
--- /dev/null
+++ b/gio/gtask.c
@@ -0,0 +1,1805 @@
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright 2011 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "gtask.h"
+
+#include "gasyncresult.h"
+#include "gcancellable.h"
+
+/**
+ * SECTION:gtask
+ * @short_description: Cancellable synchronous or asynchronous task and result
+ * @include: gio/gio.h
+ * @see_also: #GAsyncResult
+ *
+ * <para>
+ *   A #GTask represents and manages a cancellable "task".
+ * </para>
+ * <refsect2>
+ *   <title>Asynchronous operations</title>
+ *   <para>
+ *     The most common usage of #GTask is as a #GAsyncResult, to
+ *     manage data during an asynchronous operation. You call
+ *     g_task_new() in the "start" method, followed by
+ *     g_task_set_task_data() and the like if you need to keep some
+ *     additional data associated with the task, and then pass the
+ *     task object around through your asynchronous operation.
+ *     Eventually, you will call a method such as
+ *     g_task_return_pointer() or g_task_return_error(), which will
+ *     save the value you give it and then invoke the task's callback
+ *     function (waiting until the next next iteration of the main
+ *     loop first, if necessary). The caller will pass the #GTask back
+ *     to the operation's finish function (as a #GAsyncResult), and
+ *     you can use g_task_propagate_pointer() or the like to extract
+ *     the return value.
+ *   </para>
+ *   <example id="gtask-async"><title>GTask as a GAsyncResult</title>
+ *   <programlisting>
+ *     typedef struct {
+ *       CakeFrostingType frosting;
+ *       char *message;
+ *     } DecorationData;
+ *
+ *     static void
+ *     decoration_data_free (DecorationData *decoration)
+ *     {
+ *       g_free (decoration->message);
+ *       g_slice_free (DecorationData, decoration);
+ *     }
+ *
+ *     static void
+ *     baked_cb (Cake     *cake,
+ *               gpointer  user_data)
+ *     {
+ *       GTask *task = user_data;
+ *       DecorationData *decoration = g_task_get_task_data (task);
+ *       GError *error = NULL;
+ *
+ *       if (cake == NULL)
+ *         {
+ *           g_task_return_new_error (task, BAKER_ERROR, BAKER_ERROR_NO_FLOUR,
+ *                                    "Go to the supermarket");
+ *           g_object_unref (task);
+ *           return;
+ *         }
+ *
+ *       if (!cake_decorate (cake, decoration->frosting, decoration->message, &error))
+ *         {
+ *           g_object_unref (cake);
+ *           /&ast; g_task_return_error() takes ownership of error &ast;/
+ *           g_task_return_error (task, error);
+ *           g_object_unref (task);
+ *           return;
+ *         }
+ *
+ *       g_task_return_pointer (result, cake, g_object_unref);
+ *       g_object_unref (task);
+ *     }
+ *
+ *     void
+ *     baker_bake_cake_async (Baker               *self,
+ *                            guint                radius,
+ *                            CakeFlavor           flavor,
+ *                            CakeFrostingType     frosting,
+ *                            const char          *message,
+ *                            GCancellable        *cancellable,
+ *                            GAsyncReadyCallback  callback,
+ *                            gpointer             user_data)
+ *     {
+ *       GTask *task;
+ *       DecorationData *decoration;
+ *       Cake  *cake;
+ *
+ *       task = g_task_new (self, cancellable, callback, user_data);
+ *       if (radius < 3)
+ *         {
+ *           g_task_return_new_error (task, BAKER_ERROR, BAKER_ERROR_TOO_SMALL,
+ *                                    "%ucm radius cakes are silly",
+ *                                    radius);
+ *           g_object_unref (task);
+ *           return;
+ *         }
+ *
+ *       cake = _baker_get_cached_cake (self, radius, flavor, frosting, message);
+ *       if (cake != NULL)
+ *         {
+ *           /&ast; _baker_get_cached_cake() returns a reffed cake &ast;/
+ *           g_task_return_pointer (task, cake, g_object_unref);
+ *           g_object_unref (task);
+ *           return;
+ *         }
+ *
+ *       decoration = g_slice_new (DecorationData);
+ *       decoration->frosting = frosting;
+ *       decoration->message = g_strdup (message);
+ *       g_task_set_task_data (task, decoration, (GDestroyNotify) decoration_data_free);
+ *
+ *       _baker_begin_cake (self, radius, flavor, cancellable, baked_cb, task);
+ *     }
+ *
+ *     Cake *
+ *     baker_bake_cake_finish (Baker         *self,
+ *                             GAsyncResult  *result,
+ *                             GError       **error)
+ *     {
+ *       g_return_val_if_fail (g_task_is_valid (result, self), NULL);
+ *
+ *       return g_task_propagate_pointer (G_TASK (result), error);
+ *     }
+ *   </programlisting>
+ *   </example>
+ * </refsect2>
+ * <refsect2>
+ *   <title>Chained asynchronous operations</title>
+ *   <para>
+ *     #GTask also tries to simplify asynchronous operations that
+ *     internally chain together several smaller asynchronous
+ *     operations. g_task_get_cancellable(), g_task_get_context(), and
+ *     g_task_get_priority() allow you to get back the task's
+ *     #GCancellable, #GMainContext, and <link
+ *     linkend="io-priority">I/O priority</link> when starting a new
+ *     subtask, so you don't have to keep track of them yourself.
+ *     g_task_attach_source() simplifies the case of waiting for a
+ *     source to fire (automatically using the correct #GMainContext
+ *     and priority).
+ *   </para>
+ *   <example id="gtask-chained"><title>Chained asynchronous operations</title>
+ *   <programlisting>
+ *     typedef struct {
+ *       Cake *cake;
+ *       CakeFrostingType frosting;
+ *       char *message;
+ *     } BakingData;
+ *
+ *     static void
+ *     decoration_data_free (BakingData *bd)
+ *     {
+ *       if (bd->cake)
+ *         g_object_unref (bd->cake);
+ *       g_free (bd->message);
+ *       g_slice_free (BakingData, bd);
+ *     }
+ *
+ *     static void
+ *     decorated_cb (Cake         *cake,
+ *                   GAsyncResult *result,
+ *                   gpointer      user_data)
+ *     {
+ *       GTask *task = user_data;
+ *       GError *error = NULL;
+ *
+ *       if (!cake_decorate_finish (cake, result, &error))
+ *         {
+ *           g_object_unref (cake);
+ *           g_task_return_error (task, error);
+ *           g_object_unref (task);
+ *           return;
+ *         }
+ *
+ *       /&ast; baking_data_free() will drop its ref on the cake, so
+ *        &ast; we have to take another here to give to the caller.
+ *        &ast;/
+ *       g_task_return_pointer (result, g_object_ref (cake), g_object_unref);
+ *       g_object_unref (task);
+ *     }
+ *
+ *     static void
+ *     decorator_ready (gpointer user_data)
+ *     {
+ *       GTask *task = user_data;
+ *       BakingData *bd = g_task_get_task_data (task);
+ *
+ *       cake_decorate_async (bd->cake, bd->frosting, bd->message,
+ *                            g_task_get_cancellable (task),
+ *                            decorated_cb, task);
+ *     }
+ *
+ *     static void
+ *     baked_cb (Cake     *cake,
+ *               gpointer  user_data)
+ *     {
+ *       GTask *task = user_data;
+ *       BakingData *bd = g_task_get_task_data (task);
+ *       GError *error = NULL;
+ *
+ *       if (cake == NULL)
+ *         {
+ *           g_task_return_new_error (task, BAKER_ERROR, BAKER_ERROR_NO_FLOUR,
+ *                                    "Go to the supermarket");
+ *           g_object_unref (task);
+ *           return;
+ *         }
+ *
+ *       bd->cake = cake;
+ *
+ *       /&ast; Bail out now if the user has already cancelled &ast;/
+ *       if (g_task_return_error_if_cancelled (g_task_get_cancellable (task)))
+ *         {
+ *           g_object_unref (task);
+ *           return;
+ *         }
+ *
+ *       if (cake_decorator_available (cake))
+ *         decorator_ready (task);
+ *       else
+ *         {
+ *           GSource *source;
+ *
+ *           source = cake_decorator_wait_source_new (cake);
+ *           /&ast; Attach @source to @task's GMainContext and have it call
+ *            &ast; decorator_ready() when it is ready.
+ *            &ast;/
+ *           g_task_attach_source (task, source,
+ *                                 G_CALLBACK (decorator_ready));
+ *           g_source_unref (source);
+ *         }
+ *     }
+ *
+ *     void
+ *     baker_bake_cake_async (Baker               *self,
+ *                            guint                radius,
+ *                            CakeFlavor           flavor,
+ *                            CakeFrostingType     frosting,
+ *                            const char          *message,
+ *                            gint                 priority,
+ *                            GCancellable        *cancellable,
+ *                            GAsyncReadyCallback  callback,
+ *                            gpointer             user_data)
+ *     {
+ *       GTask *task;
+ *       BakingData *bd;
+ *
+ *       task = g_task_new (self, cancellable, callback, user_data);
+ *       g_task_set_priority (task, priority);
+ *
+ *       bd = g_slice_new0 (BakingData);
+ *       bd->frosting = frosting;
+ *       bd->message = g_strdup (message);
+ *       g_task_set_task_data (task, bd, (GDestroyNotify) baking_data_free);
+ *
+ *       _baker_begin_cake (self, radius, flavor, cancellable, baked_cb, task);
+ *     }
+ *
+ *     Cake *
+ *     baker_bake_cake_finish (Baker         *self,
+ *                             GAsyncResult  *result,
+ *                             GError       **error)
+ *     {
+ *       g_return_val_if_fail (g_task_is_valid (result, self), NULL);
+ *
+ *       return g_task_propagate_pointer (G_TASK (result), error);
+ *     }
+ *   </programlisting>
+ *   </example>
+ * </refsect2>
+ * <refsect2>
+ *   <title>Asynchronous operations from synchronous ones</title>
+ *   <para>
+ *     You can use g_task_run_in_thread() to turn a synchronous
+ *     operation into an asynchronous one, by running it in a thread
+ *     which will then dispatch the result back to the caller's
+ *     #GMainContext when it completes.
+ *   </para>
+ *   <example id="gtask-run-in-thread"><title>g_task_run_in_thread()</title>
+ *   <programlisting>
+ *     typedef struct {
+ *       guint radius;
+ *       CakeFlavor flavor;
+ *       CakeFrostingType frosting;
+ *       char *message;
+ *     } CakeData;
+ *
+ *     static void
+ *     cake_data_free (CakeData *cake_data)
+ *     {
+ *       g_free (cake_data->message);
+ *       g_slice_free (CakeData, cake_data);
+ *     }
+ *
+ *     static void
+ *     bake_cake_thread (GTask         *task,
+ *                       gpointer       source_object,
+ *                       gpointer       task_data,
+ *                       GCancellable  *cancellable)
+ *     {
+ *       Baker *self = source_object;
+ *       CakeData *cake_data = task_data;
+ *       Cake *cake;
+ *       GError *error = NULL;
+ *
+ *       cake = bake_cake (baker, cake_data->radius, cake_data->flavor,
+ *                         cake_data->frosting, cake_data->message,
+ *                         cancellable, &error);
+ *       if (cake)
+ *         g_task_return_pointer (task, cake, g_object_unref);
+ *       else
+ *         g_task_return_error (task, error);
+ *     }
+ *
+ *     void
+ *     baker_bake_cake_async (Baker               *self,
+ *                            guint                radius,
+ *                            CakeFlavor           flavor,
+ *                            CakeFrostingType     frosting,
+ *                            const char          *message,
+ *                            GCancellable        *cancellable,
+ *                            GAsyncReadyCallback  callback,
+ *                            gpointer             user_data)
+ *     {
+ *       CakeData *cake_data;
+ *       GTask *task;
+ *
+ *       cake_data = g_slice_new (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);
+ *     }
+ *
+ *     Cake *
+ *     baker_bake_cake_finish (Baker         *self,
+ *                             GAsyncResult  *result,
+ *                             GError       **error)
+ *     {
+ *       g_return_val_if_fail (g_task_is_valid (result, self), NULL);
+ *
+ *       return g_task_propagate_pointer (G_TASK (result), error);
+ *     }
+ *   </programlisting>
+ *   </example>
+ * </refsect2>
+ * <refsect2>
+ *   <title>Adding cancellability to uncancellable tasks</title>
+ *   <para>
+ *     Finally, g_task_run_in_thread() and g_task_run_in_thread_sync()
+ *     can be used to turn an uncancellable operation into a
+ *     cancellable one. If you call g_task_set_return_on_cancel(),
+ *     passing %TRUE, then if the task's #GCancellable is cancelled,
+ *     it will return control back to the caller immediately, while
+ *     allowing the task thread to continue running in the background
+ *     (and simply discarding its result when it finally does finish).
+ *     Provided that the task thread is careful about how it uses
+ *     locks and other externally-visible resources, this allows you
+ *     to make "GLib-friendly" asynchronous and cancellable
+ *     synchronous variants of blocking APIs.
+ *   </para>
+ *   <example id="gtask-cancellable"><title>g_task_set_return_on_cancel()</title>
+ *   <programlisting>
+ *     static void
+ *     bake_cake_thread (GTask         *task,
+ *                       gpointer       source_object,
+ *                       gpointer       task_data,
+ *                       GCancellable  *cancellable)
+ *     {
+ *       Baker *self = source_object;
+ *       CakeData *cake_data = task_data;
+ *       Cake *cake;
+ *       GError *error = NULL;
+ *
+ *       cake = bake_cake (baker, cake_data->radius, cake_data->flavor,
+ *                         cake_data->frosting, cake_data->message,
+ *                         &error);
+ *       if (error)
+ *         {
+ *           g_task_return_error (task, error);
+ *           return;
+ *         }
+ *
+ *       /&ast; If the task has already been cancelled, then we don't
+ *        &ast; want to add the cake to the cake cache. Likewise, we don't
+ *        &ast; want to have the task get cancelled in the middle of
+ *        &ast; updating the cache. g_task_set_return_on_cancel() will
+ *        &ast; return %TRUE here if it managed to disable return-on-cancel,
+ *        &ast; or %FALSE if the task was cancelled before it could.
+ *        &ast;/
+ *       if (g_task_set_return_on_cancel (task, FALSE))
+ *         {
+ *           /&ast; If the caller cancels at this point, their
+ *            &ast; GAsyncReadyCallback won't be invoked until we return,
+ *            &ast; so we don't have to worry that this code will run at
+ *            &ast; the same time as that code does. But if there were
+ *            &ast; other functions that might look at the cake cache,
+ *            &ast; then we'd probably need a GMutex here as well.
+ *            &ast;/
+ *           baker_add_cake_to_cache (baker, cake);
+ *           g_task_return_pointer (task, cake, g_object_unref);
+ *         }
+ *     }
+ *
+ *     void
+ *     baker_bake_cake_async (Baker               *self,
+ *                            guint                radius,
+ *                            CakeFlavor           flavor,
+ *                            CakeFrostingType     frosting,
+ *                            const char          *message,
+ *                            GCancellable        *cancellable,
+ *                            GAsyncReadyCallback  callback,
+ *                            gpointer             user_data)
+ *     {
+ *       CakeData *cake_data;
+ *       GTask *task;
+ *
+ *       cake_data = g_slice_new (CakeData);
+ *       /&ast; ... &ast;/
+ *
+ *       task = g_task_new (self, cancellable, callback, user_data);
+ *       g_task_set_task_data (task, cake_data, (GDestroyNotify) cake_data_free);
+ *       g_task_set_return_on_cancel (task, TRUE);
+ *       g_task_run_in_thread (task, bake_cake_thread);
+ *     }
+ *
+ *     Cake *
+ *     baker_bake_cake_sync (Baker               *self,
+ *                           guint                radius,
+ *                           CakeFlavor           flavor,
+ *                           CakeFrostingType     frosting,
+ *                           const char          *message,
+ *                           GCancellable        *cancellable,
+ *                           GError             **error)
+ *     {
+ *       CakeData *cake_data;
+ *       GTask *task;
+ *       Cake *cake;
+ *
+ *       cake_data = g_slice_new (CakeData);
+ *       /&ast; ... &ast;/
+ *
+ *       task = g_task_new (self, cancellable, NULL, NULL);
+ *       g_task_set_task_data (task, cake_data, (GDestroyNotify) cake_data_free);
+ *       g_task_set_return_on_cancel (task, TRUE);
+ *       g_task_run_in_thread_sync (task, bake_cake_thread);
+ *
+ *       cake = g_task_propagate_pointer (task, error);
+ *       g_object_unref (task);
+ *       return cake;
+ *     }
+ *   </programlisting>
+ *   </example>
+ * </refsect2>
+ * <refsect2>
+ *   <title>Porting from <literal>GSimpleAsyncResult</literal></title>
+ *   <para>
+ *     #GTask's API attempts to be simpler than #GSimpleAsyncResult's
+ *     in several ways:
+ *   </para>
+ *   <itemizedlist>
+ *     <listitem><para>
+ *       You can save task-specific data with g_task_set_task_data(), and
+ *       retrieve it later with g_task_get_task_data(). This replaces the
+ *       abuse of g_simple_async_result_set_op_res_gpointer() for the same
+ *       purpose with #GSimpleAsyncResult.
+ *     </para></listitem>
+ *     <listitem><para>
+ *       In addition to the task data, #GTask also keeps track of the
+ *       <link linkend="io-priority">priority</link>, #GCancellable, and
+ *       #GMainContext associated with the task, so tasks that consist of
+ *       a chain of simpler asynchronous operations will have easy access
+ *       to those values when starting each sub-task.
+ *     </para></listitem>
+ *     <listitem><para>
+ *       g_task_return_error_if_cancelled() provides simplified
+ *       handling for cancellation. In addition, cancellation
+ *       overrides any other #GTask return value by default, like
+ *       #GSimpleAsyncResult does when
+ *       g_simple_async_result_set_check_cancellable() is called.
+ *       (You can use g_task_set_check_cancellable() to turn off that
+ *       behavior.) On the other hand, g_task_run_in_thread()
+ *       guarantees that it will always run your
+ *       <literal>task_func</literal>, even if the task's #GCancellable
+ *       is already cancelled before the task gets a chance to run;
+ *       you can start your <literal>task_func</literal> with a
+ *       g_task_return_error_if_cancelled() check if you need the
+ *       old behavior.
+ *     </para></listitem>
+ *     <listitem><para>
+ *       The "return" methods (eg, g_task_return_pointer())
+ *       automatically cause the task to be "completed" as well, and
+ *       there is no need to worry about the "complete" vs "complete
+ *       in idle" distinction. (#GTask automatically figures out
+ *       whether the task's callback can be invoked directly, or
+ *       if it needs to be sent to another #GMainContext, or delayed
+ *       until the next iteration of the current #GMainContext.)
+ *     </para></listitem>
+ *     <listitem><para>
+ *       The "finish" functions for #GTask-based operations are generally
+ *       much simpler than #GSimpleAsyncResult ones, normally consisting
+ *       of only a single call to g_task_propagate_pointer() or the like.
+ *       Since g_task_propagate_pointer() "steals" the return value from
+ *       the #GTask, it is not necessary to juggle pointers around to
+ *       prevent it from being freed twice.
+ *     </para></listitem>
+ *     <listitem><para>
+ *       With #GSimpleAsyncResult, it was common to call
+ *       g_simple_async_result_propagate_error() from the
+ *       <literal>_finish()</literal> wrapper function, and have
+ *       virtual method implementations only deal with successful
+ *       returns. This behavior is deprecated, because it makes it
+ *       difficult for a subclass to chain to a parent class's async
+ *       methods. Instead, the wrapper function should just be a
+ *       simple wrapper, and the virtual method should call an
+ *       appropriate <literal>g_task_propagate_</literal> function.
+ *       Note that wrapper methods can now use
+ *       g_async_result_legacy_propagate_error() to do old-style
+ *       #GSimpleAsyncResult error-returning behavior, and
+ *       g_async_result_is_tagged() to check if a result is tagged as
+ *       having come from the <literal>_async()</literal> wrapper
+ *       function (for "short-circuit" results, such as when passing
+ *       0 to g_input_stream_read_async()).
+ *     </para></listitem>
+ *   </itemizedlist>
+ * </refsect2>
+ */
+
+/**
+ * GTask:
+ *
+ * The opaque object representing a synchronous or asynchronous task
+ * and its result.
+ */
+
+struct _GTask {
+  GObject parent_instance;
+
+  gpointer source_object;
+  gpointer source_tag;
+
+  gpointer task_data;
+  GDestroyNotify task_data_destroy;
+
+  GMainContext *context;
+  guint64 creation_time;
+  gint priority;
+  GCancellable *cancellable;
+  gboolean check_cancellable;
+
+  GAsyncReadyCallback callback;
+  gpointer callback_data;
+
+  GTaskThreadFunc task_func;
+  GMutex lock;
+  GCond cond;
+  gboolean return_on_cancel;
+  gboolean thread_cancelled;
+  gboolean synchronous;
+  gboolean thread_complete;
+
+  GError *error;
+  union {
+    gpointer pointer;
+    gssize   size;
+    gboolean boolean;
+  } result;
+  GDestroyNotify result_destroy;
+  gboolean result_set;
+};
+
+#define G_TASK_IS_THREADED(task) ((task)->task_func != NULL)
+
+struct _GTaskClass
+{
+  GObjectClass parent_class;
+};
+
+static void g_task_thread_pool_resort (void);
+
+static void g_task_async_result_iface_init (GAsyncResultIface *iface);
+static void g_task_thread_pool_init (void);
+
+G_DEFINE_TYPE_WITH_CODE (GTask, g_task, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_RESULT,
+                                                g_task_async_result_iface_init);
+                         g_task_thread_pool_init ();)
+
+static GThreadPool *task_pool;
+
+static void
+g_task_init (GTask *task)
+{
+  task->check_cancellable = TRUE;
+}
+
+static void
+g_task_finalize (GObject *object)
+{
+  GTask *task = G_TASK (object);
+
+  g_clear_object (&task->source_object);
+  g_clear_object (&task->cancellable);
+
+  if (task->context)
+    g_main_context_unref (task->context);
+
+  if (task->task_data_destroy)
+    task->task_data_destroy (task->task_data);
+
+  if (task->result_destroy && task->result.pointer)
+    task->result_destroy (task->result.pointer);
+
+  if (G_TASK_IS_THREADED (task))
+    {
+      g_mutex_clear (&task->lock);
+      g_cond_clear (&task->cond);
+    }
+
+  G_OBJECT_CLASS (g_task_parent_class)->finalize (object);
+}
+
+/**
+ * g_task_new:
+ * @source_object: (allow-none): the #GObject that owns this task, or %NULL.
+ * @cancellable: (allow-none): optional #GCancellable object, %NULL to ignore.
+ * @callback: (scope async): a #GAsyncReadyCallback.
+ * @callback_data: (closure): user data passed to @callback.
+ *
+ * Creates a #GTask acting on @source_object, which will eventually be
+ * used to invoke @callback in the current <link
+ * linkend="g-main-context-push-thread-default">thread-default main
+ * context</link>.
+ *
+ * Call this in the "start" method of your asynchronous method, and
+ * pass the #GTask around throughout the asynchronous operation. You
+ * can use g_task_set_task_data() to attach task-specific data to the
+ * object, which you can retrieve later via g_task_get_task_data().
+ *
+ * By default, if @cancellable is cancelled, then the return value of
+ * the task will always be %G_IO_ERROR_CANCELLED, even if the task had
+ * already completed before the cancellation. This allows for
+ * simplified handling in cases where cancellation may imply that
+ * other objects that the task depends on have been destroyed. If you
+ * do not want this behavior, you can use
+ * g_task_set_check_cancellable() to change it.
+ *
+ * Returns: a #GTask.
+ *
+ * Since: 2.36
+ */
+GTask *
+g_task_new (gpointer              source_object,
+            GCancellable         *cancellable,
+            GAsyncReadyCallback   callback,
+            gpointer              callback_data)
+{
+  GTask *task;
+  GSource *source;
+
+  task = g_object_new (G_TYPE_TASK, NULL);
+  task->source_object = source_object ? g_object_ref (source_object) : NULL;
+  task->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+  task->callback = callback;
+  task->callback_data = callback_data;
+  task->context = g_main_context_ref_thread_default ();
+
+  source = g_main_current_source ();
+  if (source)
+    task->creation_time = g_source_get_time (source);
+
+  return task;
+}
+
+/**
+ * g_task_report_error:
+ * @source_object: (allow-none): the #GObject that owns this task, or %NULL.
+ * @callback: (scope async): a #GAsyncReadyCallback.
+ * @callback_data: (closure): user data passed to @callback.
+ * @source_tag: an opaque pointer indicating the source of this task
+ * @error: (transfer full): error to report
+ *
+ * Creates a #GTask and then immediately calls g_task_return_error()
+ * on it. Use this in the wrapper function of an asynchronous method
+ * when you want to avoid even calling the virtual method. You can
+ * then use g_async_result_is_tagged() in the finish method wrapper to
+ * check if the result there is tagged as having been created by the
+ * wrapper method, and deal with it appropriately if so.
+ *
+ * See also g_task_report_new_error().
+ *
+ * Since: 2.36
+ */
+void
+g_task_report_error (gpointer             source_object,
+                     GAsyncReadyCallback  callback,
+                     gpointer             callback_data,
+                     gpointer             source_tag,
+                     GError              *error)
+{
+  GTask *task;
+
+  task = g_task_new (source_object, NULL, callback, callback_data);
+  g_task_set_source_tag (task, source_tag);
+  g_task_return_error (task, error);
+  g_object_unref (task);
+}
+
+/**
+ * g_task_report_new_error:
+ * @source_object: (allow-none): the #GObject that owns this task, or %NULL.
+ * @callback: (scope async): a #GAsyncReadyCallback.
+ * @callback_data: (closure): user data passed to @callback.
+ * @source_tag: an opaque pointer indicating the source of this task
+ * @domain: a #GQuark.
+ * @code: an error code.
+ * @format: a string with format characters.
+ * @...: a list of values to insert into @format.
+ *
+ * Creates a #GTask and then immediately calls
+ * g_task_return_new_error() on it. Use this in the wrapper function
+ * of an asynchronous method when you want to avoid even calling the
+ * virtual method. You can then use g_async_result_is_tagged() in the
+ * finish method wrapper to check if the result there is tagged as
+ * having been created by the wrapper method, and deal with it
+ * appropriately if so.
+ *
+ * See also g_task_report_error().
+ *
+ * Since: 2.36
+ */
+void
+g_task_report_new_error (gpointer             source_object,
+                         GAsyncReadyCallback  callback,
+                         gpointer             callback_data,
+                         gpointer             source_tag,
+                         GQuark               domain,
+                         gint                 code,
+                         const char          *format,
+                         ...)
+{
+  GError *error;
+  va_list ap;
+
+  va_start (ap, format);
+  error = g_error_new_valist (domain, code, format, ap);
+  va_end (ap);
+
+  g_task_report_error (source_object, callback, callback_data,
+                       source_tag, error);
+}
+
+/**
+ * g_task_set_task_data:
+ * @task: the #GTask
+ * @task_data: (allow-none): task-specific data
+ * @task_data_destroy: (allow-none): #GDestroyNotify for @task_data
+ *
+ * Sets @task's task data (freeing the existing task data, if any).
+ *
+ * Since: 2.36
+ */
+void
+g_task_set_task_data (GTask          *task,
+                      gpointer        task_data,
+                      GDestroyNotify  task_data_destroy)
+{
+  if (task->task_data_destroy)
+    task->task_data_destroy (task->task_data);
+
+  task->task_data = task_data;
+  task->task_data_destroy = task_data_destroy;
+}
+
+/**
+ * g_task_set_priority:
+ * @task: the #GTask
+ * @priority: the <link linkend="io-priority">priority</link>
+ *   of the request.
+ *
+ * Sets @task's priority. If you do not call this, it will default to
+ * %G_PRIORITY_DEFAULT.
+ *
+ * This will affect the priority of #GSources created with
+ * g_task_attach_source() and the scheduling of tasks run in threads,
+ * and can also be explicitly retrieved later via
+ * g_task_get_priority().
+ *
+ * Since: 2.36
+ */
+void
+g_task_set_priority (GTask *task,
+                     gint   priority)
+{
+  task->priority = priority;
+}
+
+/**
+ * g_task_set_check_cancellable:
+ * @task: the #GTask
+ * @check_cancellable: whether #GTask will check the state of
+ *   its #GCancellable for you.
+ *
+ * Sets or clears @task's check-cancellable flag. If this is %TRUE
+ * (the default), then g_task_propagate_pointer(), etc, and
+ * g_task_had_error() will check the task's #GCancellable first, and
+ * if it has been cancelled, then they will consider the task to have
+ * returned an "Operation was cancelled" error
+ * (%G_IO_ERROR_CANCELLED), regardless of any other error or return
+ * value the task may have had.
+ *
+ * If @check_cancellable is %FALSE, then the #GTask will not check the
+ * cancellable itself, and it is up to @task's owner to do this (eg,
+ * via g_task_return_error_if_cancelled()).
+ *
+ * If you are using g_task_set_return_on_cancel() as well, then
+ * you must leave check-cancellable set %TRUE.
+ *
+ * Since: 2.36
+ */
+void
+g_task_set_check_cancellable (GTask    *task,
+                              gboolean  check_cancellable)
+{
+  g_return_if_fail (check_cancellable || !task->return_on_cancel);
+
+  task->check_cancellable = check_cancellable;
+}
+
+static void g_task_thread_complete (GTask *task);
+
+/**
+ * g_task_set_return_on_cancel:
+ * @task: the #GTask
+ * @return_on_cancel: whether the task returns automatically when
+ *   it is cancelled.
+ *
+ * Sets or clears @task's return-on-cancel flag. This is only
+ * meaningful for tasks run via g_task_run_in_thread() or
+ * g_task_run_in_thread_sync().
+ *
+ * If @return_on_cancel is %TRUE, then cancelling @task's
+ * #GCancellable will immediately cause it to return, as though the
+ * task's #GTaskThreadFunc had called
+ * g_task_return_error_if_cancelled() and then returned.
+ *
+ * This allows you to create a cancellable wrapper around an
+ * uninterruptable function. The #GTaskThreadFunc just needs to be
+ * careful that it does not modify any externally-visible state after
+ * it has been cancelled. To do that, the thread should call
+ * g_task_set_return_on_cancel() again to (atomically) set
+ * return-on-cancel %FALSE before making externally-visible changes;
+ * if the task gets cancelled before the return-on-cancel flag could
+ * be changed, g_task_set_return_on_cancel() will indicate this by
+ * returning %FALSE.
+ *
+ * You can disable and re-enable this flag multiple times if you wish.
+ * If the task's #GCancellable is cancelled while return-on-cancel is
+ * %FALSE, then calling g_task_set_return_on_cancel() to set it %TRUE
+ * again will cause the task to be cancelled at that point.
+ *
+ * If the task's #GCancellable is already cancelled before you call
+ * g_task_run_in_thread()/g_task_run_in_thread_sync(), then the
+ * #GTaskThreadFunc will still be run (for consistency), but the task
+ * will also be completed right away.
+ *
+ * Returns: %TRUE if @task's return-on-cancel flag was changed to
+ *   match @return_on_cancel. %FALSE if @task has already been
+ *   cancelled.
+ *
+ * Since: 2.36
+ */
+gboolean
+g_task_set_return_on_cancel (GTask    *task,
+                             gboolean  return_on_cancel)
+{
+  g_return_val_if_fail (task->check_cancellable || !return_on_cancel, FALSE);
+
+  if (!G_TASK_IS_THREADED (task))
+    {
+      task->return_on_cancel = return_on_cancel;
+      return TRUE;
+    }
+
+  g_mutex_lock (&task->lock);
+  if (task->thread_cancelled)
+    {
+      if (return_on_cancel && !task->return_on_cancel)
+        {
+          g_mutex_unlock (&task->lock);
+          g_task_thread_complete (task);
+        }
+      else
+        g_mutex_unlock (&task->lock);
+      return FALSE;
+    }
+  task->return_on_cancel = return_on_cancel;
+  g_mutex_unlock (&task->lock);
+
+  return TRUE;
+}
+
+/**
+ * g_task_set_source_tag:
+ * @task: the #GTask
+ * @source_tag: an opaque pointer indicating the source of this task
+ *
+ * Sets @task's source tag. You can use this to tag a task return
+ * value with a particular pointer (usually a pointer to the function
+ * doing the tagging) and then later check it using
+ * g_task_get_source_tag() (or g_async_result_is_tagged()) in the
+ * task's "finish" function, to figure out if the response came from a
+ * particular place.
+ *
+ * Since: 2.36
+ */
+void
+g_task_set_source_tag (GTask    *task,
+                       gpointer  source_tag)
+{
+  task->source_tag = source_tag;
+}
+
+/**
+ * g_task_get_source_object:
+ * @task: a #GTask
+ *
+ * Gets the source object from @task. Like
+ * g_async_result_get_source_object(), but does not ref the object.
+ *
+ * Returns: (transfer none): @task's source object, or %NULL
+ *
+ * Since: 2.36
+ */
+gpointer
+g_task_get_source_object (GTask *task)
+{
+  return task->source_object;
+}
+
+static GObject *
+g_task_ref_source_object (GAsyncResult *res)
+{
+  GTask *task = G_TASK (res);
+
+  if (task->source_object)
+    return g_object_ref (task->source_object);
+  else
+    return NULL;
+}
+
+/**
+ * g_task_get_task_data:
+ * @task: a #GTask
+ *
+ * Gets @task's <literal>task_data</literal>.
+ *
+ * Returns: (transfer none): @task's <literal>task_data</literal>.
+ *
+ * Since: 2.36
+ */
+gpointer
+g_task_get_task_data (GTask *task)
+{
+  return task->task_data;
+}
+
+/**
+ * g_task_get_priority:
+ * @task: a #GTask
+ *
+ * Gets @task's priority
+ *
+ * Returns: @task's priority
+ *
+ * Since: 2.36
+ */
+gint
+g_task_get_priority (GTask *task)
+{
+  return task->priority;
+}
+
+/**
+ * g_task_get_context:
+ * @task: a #GTask
+ *
+ * Gets the #GMainContext that @task will return its result in (that
+ * is, the context that was the <link
+ * linkend="g-main-context-push-thread-default">thread-default main
+ * context</link> at the point when @task was created).
+ *
+ * This will always return a non-%NULL value, even if the task's
+ * context is the default #GMainContext.
+ *
+ * Returns: (transfer none): @task's #GMainContext
+ *
+ * Since: 2.36
+ */
+GMainContext *
+g_task_get_context (GTask *task)
+{
+  return task->context;
+}
+
+/**
+ * g_task_get_cancellable:
+ * @task: a #GTask
+ *
+ * Gets @task's #GCancellable
+ *
+ * Returns: (transfer none): @task's #GCancellable
+ *
+ * Since: 2.36
+ */
+GCancellable *
+g_task_get_cancellable (GTask *task)
+{
+  return task->cancellable;
+}
+
+/**
+ * g_task_get_check_cancellable:
+ * @task: the #GTask
+ *
+ * Gets @task's check-cancellable flag. See
+ * g_task_set_check_cancellable() for more details.
+ *
+ * Since: 2.36
+ */
+gboolean
+g_task_get_check_cancellable (GTask *task)
+{
+  return task->check_cancellable;
+}
+
+/**
+ * g_task_get_return_on_cancel:
+ * @task: the #GTask
+ *
+ * Gets @task's return-on-cancel flag. See
+ * g_task_set_return_on_cancel() for more details.
+ *
+ * Since: 2.36
+ */
+gboolean
+g_task_get_return_on_cancel (GTask *task)
+{
+  return task->return_on_cancel;
+}
+
+/**
+ * g_task_get_source_tag:
+ * @task: a #GTask
+ *
+ * Gets @task's source tag. See g_task_set_source_tag().
+ *
+ * Return value: (transfer none): @task's source tag
+ *
+ * Since: 2.36
+ */
+gpointer
+g_task_get_source_tag (GTask *task)
+{
+  return task->source_tag;
+}
+
+
+static void
+g_task_return_now (GTask *task)
+{
+  g_main_context_push_thread_default (task->context);
+  task->callback (task->source_object,
+                  G_ASYNC_RESULT (task),
+                  task->callback_data);
+  g_main_context_pop_thread_default (task->context);
+}
+
+static gboolean
+complete_in_idle_cb (gpointer task)
+{
+  g_task_return_now (task);
+  g_object_unref (task);
+  return FALSE;
+}
+
+typedef enum {
+  G_TASK_RETURN_SUCCESS,
+  G_TASK_RETURN_ERROR,
+  G_TASK_RETURN_FROM_THREAD
+} GTaskReturnType;
+
+static void
+g_task_return (GTask           *task,
+               GTaskReturnType  type)
+{
+  GSource *source;
+
+  if (type == G_TASK_RETURN_SUCCESS)
+    task->result_set = TRUE;
+
+  if (task->synchronous || !task->callback)
+    return;
+
+  /* Normally we want to invoke the task's callback when its return
+   * value is set. But if the task is running in a thread, then we
+   * want to wait until after the task_func returns, to simplify
+   * locking/refcounting/etc.
+   */
+  if (G_TASK_IS_THREADED (task) && type != G_TASK_RETURN_FROM_THREAD)
+    return;
+
+  g_object_ref (task);
+
+  /* See if we can complete the task immediately. First, we have to be
+   * running inside the task's thread/GMainContext.
+   */
+  source = g_main_current_source ();
+  if (source && g_source_get_context (source) == task->context)
+    {
+      /* Second, we can only complete immediately if this is not the
+       * same iteration of the main loop that the task was created in.
+       */
+      if (g_source_get_time (source) > task->creation_time)
+        {
+          g_task_return_now (task);
+          g_object_unref (task);
+          return;
+        }
+    }
+
+  /* Otherwise, complete in the next iteration */
+  source = g_idle_source_new ();
+  g_task_attach_source (task, source, complete_in_idle_cb);
+  g_source_unref (source);
+}
+
+
+/**
+ * GTaskThreadFunc:
+ * @task: the #GTask
+ * @source_object: @task's source object
+ * @task_data: @task's task data
+ * @cancellable: @task's #GCancellable, or %NULL
+ *
+ * The prototype for a task function to be run in a thread via
+ * g_task_run_in_thread() or g_task_run_in_thread_sync().
+ *
+ * If the return-on-cancel flag is set on @task, and @cancellable gets
+ * cancelled, then the #GTask will be completed immediately (as though
+ * g_task_return_error_if_cancelled() had been called), without
+ * waiting for the task function to complete. However, the task
+ * function will continue running in its thread in the background. The
+ * function therefore needs to be careful about how it uses
+ * externally-visible state in this case. See
+ * g_task_set_return_on_cancel() for more details.
+ *
+ * Other than in that case, @task will be completed when the
+ * #GTaskThreadFunc returns, <emphasis>not</emphasis> when it calls
+ * a <literal>g_task_return_</literal> function.
+ *
+ * Since: 2.36
+ */
+
+static void task_thread_cancelled (GCancellable *cancellable,
+                                   gpointer      user_data);
+
+static void
+g_task_thread_complete (GTask *task)
+{
+  g_mutex_lock (&task->lock);
+  if (task->thread_complete)
+    {
+      /* The task belatedly completed after having been cancelled
+       * (or was cancelled in the midst of being completed).
+       */
+      g_mutex_unlock (&task->lock);
+      return;
+    }
+
+  task->thread_complete = TRUE;
+  g_mutex_unlock (&task->lock);
+
+  if (task->cancellable)
+    g_signal_handlers_disconnect_by_func (task->cancellable, task_thread_cancelled, task);
+
+  if (task->synchronous)
+    g_cond_signal (&task->cond);
+  else
+    g_task_return (task, G_TASK_RETURN_FROM_THREAD);
+}
+
+static void
+g_task_thread_pool_thread (gpointer thread_data,
+                           gpointer pool_data)
+{
+  GTask *task = thread_data;
+
+  task->task_func (task, task->source_object, task->task_data,
+                   task->cancellable);
+  g_task_thread_complete (task);
+  g_object_unref (task);
+}
+
+static void
+task_thread_cancelled (GCancellable *cancellable,
+                       gpointer      user_data)
+{
+  GTask *task = user_data;
+
+  g_task_thread_pool_resort ();
+
+  g_mutex_lock (&task->lock);
+  task->thread_cancelled = TRUE;
+
+  if (!task->return_on_cancel)
+    {
+      g_mutex_unlock (&task->lock);
+      return;
+    }
+
+  /* We don't actually set task->error; g_task_return_error() doesn't
+   * use a lock, and g_task_propagate_error() will call
+   * g_cancellable_set_error_if_cancelled() anyway.
+   */
+  g_mutex_unlock (&task->lock);
+  g_task_thread_complete (task);
+}
+
+static void
+task_thread_cancelled_disconnect_notify (gpointer  task,
+                                         GClosure *closure)
+{
+  g_object_unref (task);
+}
+
+static void
+g_task_start_task_thread (GTask           *task,
+                          GTaskThreadFunc  task_func)
+{
+  g_mutex_init (&task->lock);
+  g_cond_init (&task->cond);
+
+  g_mutex_lock (&task->lock);
+
+  task->task_func = task_func;
+
+  if (task->cancellable)
+    {
+      if (task->return_on_cancel &&
+          g_cancellable_set_error_if_cancelled (task->cancellable,
+                                                &task->error))
+        {
+          task->thread_cancelled = task->thread_complete = TRUE;
+          g_thread_pool_push (task_pool, g_object_ref (task), NULL);
+          return;
+        }
+
+      g_signal_connect_data (task->cancellable, "cancelled",
+                             G_CALLBACK (task_thread_cancelled),
+                             g_object_ref (task),
+                             task_thread_cancelled_disconnect_notify, 0);
+    }
+
+  g_thread_pool_push (task_pool, g_object_ref (task), &task->error);
+  if (task->error)
+    task->thread_complete = TRUE;
+}
+
+/**
+ * g_task_run_in_thread:
+ * @task: a #GTask
+ * @task_func: a #GTaskThreadFunc
+ *
+ * Runs @task_func in another thread. When @task_func returns, @task's
+ * #GAsyncReadyCallback will be invoked in @task's #GMainContext.
+ *
+ * This takes a ref on @task until the task completes.
+ *
+ * See #GTaskThreadFunc for more details about how @task_func is handled.
+ *
+ * Since: 2.36
+ */
+void
+g_task_run_in_thread (GTask           *task,
+                      GTaskThreadFunc  task_func)
+{
+  g_return_if_fail (G_IS_TASK (task));
+
+  g_object_ref (task);
+  g_task_start_task_thread (task, task_func);
+
+  /* The task may already be cancelled, or g_thread_pool_push() may
+   * have failed.
+   */
+  if (task->thread_complete)
+    {
+      g_mutex_unlock (&task->lock);
+      g_task_return (task, G_TASK_RETURN_FROM_THREAD);
+    }
+  else
+    g_mutex_unlock (&task->lock);
+
+  g_object_unref (task);
+}
+
+/**
+ * g_task_run_in_thread_sync:
+ * @task: a #GTask
+ * @task_func: a #GTaskThreadFunc
+ *
+ * Runs @task_func in another thread, and waits for it to return or be
+ * cancelled. You can use g_task_propagate_pointer(), etc, afterward
+ * to get the result of @task_func.
+ *
+ * See #GTaskThreadFunc for more details about how @task_func is handled.
+ *
+ * Normally this is used with tasks created with a %NULL
+ * <literal>callback</literal>, but note that even if the task does
+ * have a callback, it will not be invoked when @task_func returns.
+ *
+ * Since: 2.36
+ */
+void
+g_task_run_in_thread_sync (GTask           *task,
+                           GTaskThreadFunc  task_func)
+{
+  g_return_if_fail (G_IS_TASK (task));
+
+  g_object_ref (task);
+
+  task->synchronous = TRUE;
+  g_task_start_task_thread (task, task_func);
+
+  while (!task->thread_complete)
+    g_cond_wait (&task->cond, &task->lock);
+
+  g_mutex_unlock (&task->lock);
+  g_object_unref (task);
+}
+
+/**
+ * g_task_attach_source:
+ * @task: a #GTask
+ * @source: the source to attach
+ * @callback: the callback to invoke when @source triggers
+ *
+ * A utility function for dealing with async operations where you need
+ * to wait for a #GSource to trigger. Attaches @source to @task's
+ * #GMainContext with @task's <link
+ * linkend="io-priority">priority</link>, and sets @source's callback
+ * to @callback, with @task as the callback's
+ * <literal>user_data</literal>.
+ *
+ * This takes a reference on @task until @source is destroyed.
+ *
+ * Since: 2.36
+ */
+void
+g_task_attach_source (GTask       *task,
+                      GSource     *source,
+                      GSourceFunc  callback)
+{
+  g_source_set_callback (source, callback,
+                         g_object_ref (task), g_object_unref);
+  g_source_set_priority (source, task->priority);
+  g_source_attach (source, task->context);
+}
+
+
+static gboolean
+g_task_propagate_error (GTask   *task,
+                        GError **error)
+{
+  if (task->check_cancellable &&
+      g_cancellable_set_error_if_cancelled (task->cancellable, error))
+    return TRUE;
+  else if (task->error)
+    {
+      g_propagate_error (error, task->error);
+      task->error = NULL;
+      return TRUE;
+    }
+  else
+    return FALSE;
+}
+
+/**
+ * g_task_return_pointer:
+ * @task: a #GTask
+ * @result: (allow-none) (transfer full): the pointer result of a task
+ *     function
+ * @result_destroy: (allow-none): a #GDestroyNotify function.
+ *
+ * Sets @task's result to @result and completes the task. If @result
+ * is not %NULL, then @result_destroy will be used to free @result if
+ * the caller does not take ownership of it with
+ * g_task_propagate_pointer().
+ *
+ * "Completes the task" means that for an ordinary asynchronous task
+ * it will either invoke the task's callback, or else queue that
+ * callback to be invoked in the proper #GMainContext, or in the next
+ * iteration of the current #GMainContext. For a task run via
+ * g_task_run_in_thread() or g_task_run_in_thread_sync(), calling this
+ * method will save @result to be returned to the caller later, but
+ * the task will not actually be completed until the #GTaskThreadFunc
+ * exits.
+ *
+ * Note that since the task may be completed before returning from
+ * g_task_return_pointer(), you cannot assume that @result is still
+ * valid after calling this, unless you are still holding another
+ * reference on it.
+ *
+ * Since: 2.36
+ */
+void
+g_task_return_pointer (GTask          *task,
+                       gpointer        result,
+                       GDestroyNotify  result_destroy)
+{
+  g_return_if_fail (task->result_set == FALSE);
+
+  task->result.pointer = result;
+  task->result_destroy = result_destroy;
+
+  g_task_return (task, G_TASK_RETURN_SUCCESS);
+}
+
+/**
+ * g_task_propagate_pointer:
+ * @task: a #GTask
+ * @error: return location for a #GError
+ *
+ * Gets the result of @task as a pointer, and transfers ownership
+ * of that value to the caller.
+ *
+ * If the task resulted in an error, or was cancelled, then this will
+ * instead return %NULL and set @error.
+ *
+ * Since this method transfers ownership of the return value (or
+ * error) to the caller, you may only call it once.
+ *
+ * Returns: (transfer full): the task result, or %NULL on error
+ *
+ * Since: 2.36
+ */
+gpointer
+g_task_propagate_pointer (GTask   *task,
+                          GError **error)
+{
+  if (g_task_propagate_error (task, error))
+    return NULL;
+
+  g_return_val_if_fail (task->result_set == TRUE, NULL);
+
+  task->result_destroy = NULL;
+  task->result_set = FALSE;
+  return task->result.pointer;
+}
+
+/**
+ * g_task_return_int:
+ * @task: a #GTask.
+ * @result: the integer (#gssize) result of a task function.
+ *
+ * Sets @task's result to @result and completes the task (see
+ * g_task_return_pointer() for more discussion of exactly what this
+ * means).
+ *
+ * Since: 2.36
+ */
+void
+g_task_return_int (GTask  *task,
+                   gssize  result)
+{
+  g_return_if_fail (task->result_set == FALSE);
+
+  task->result.size = result;
+
+  g_task_return (task, G_TASK_RETURN_SUCCESS);
+}
+
+/**
+ * g_task_propagate_int:
+ * @task: a #GTask.
+ * @error: return location for a #GError
+ *
+ * Gets the result of @task as an integer (#gssize).
+ *
+ * If the task resulted in an error, or was cancelled, then this will
+ * instead return -1 and set @error.
+ *
+ * Since this method transfers ownership of the return value (or
+ * error) to the caller, you may only call it once.
+ *
+ * Returns: the task result, or -1 on error
+ *
+ * Since: 2.36
+ */
+gssize
+g_task_propagate_int (GTask   *task,
+                      GError **error)
+{
+  if (g_task_propagate_error (task, error))
+    return -1;
+
+  g_return_val_if_fail (task->result_set == TRUE, -1);
+
+  task->result_set = FALSE;
+  return task->result.size;
+}
+
+/**
+ * g_task_return_boolean:
+ * @task: a #GTask.
+ * @result: the #gboolean result of a task function.
+ *
+ * Sets @task's result to @result and completes the task (see
+ * g_task_return_pointer() for more discussion of exactly what this
+ * means).
+ *
+ * Since: 2.36
+ */
+void
+g_task_return_boolean (GTask    *task,
+                       gboolean  result)
+{
+  g_return_if_fail (task->result_set == FALSE);
+
+  task->result.boolean = result;
+
+  g_task_return (task, G_TASK_RETURN_SUCCESS);
+}
+
+/**
+ * g_task_propagate_boolean:
+ * @task: a #GTask.
+ * @error: return location for a #GError
+ *
+ * Gets the result of @task as a #gboolean.
+ *
+ * If the task resulted in an error, or was cancelled, then this will
+ * instead return %FALSE and set @error.
+ *
+ * Since this method transfers ownership of the return value (or
+ * error) to the caller, you may only call it once.
+ *
+ * Returns: the task result, or %FALSE on error
+ *
+ * Since: 2.36
+ */
+gboolean
+g_task_propagate_boolean (GTask   *task,
+                          GError **error)
+{
+  if (g_task_propagate_error (task, error))
+    return FALSE;
+
+  g_return_val_if_fail (task->result_set == TRUE, FALSE);
+
+  task->result_set = FALSE;
+  return task->result.boolean;
+}
+
+/**
+ * g_task_return_error:
+ * @task: a #GTask.
+ * @error: (transfer full): the #GError result of a task function.
+ *
+ * Sets @task's result to @error (which @task assumes ownership of)
+ * and completes the task (see g_task_return_pointer() for more
+ * discussion of exactly what this means).
+ *
+ * Note that since the task takes ownership of @error, and since the
+ * task may be completed before returning from g_task_return_error(),
+ * you cannot assume that @error is still valid after calling this.
+ * Call g_error_copy() on the error if you need to keep a local copy
+ * as well.
+ *
+ * See also g_task_return_new_error().
+ *
+ * Since: 2.36
+ */
+void
+g_task_return_error (GTask  *task,
+                     GError *error)
+{
+  g_return_if_fail (task->result_set == FALSE);
+  g_return_if_fail (error != NULL);
+
+  task->error = error;
+
+  g_task_return (task, G_TASK_RETURN_ERROR);
+}
+
+/**
+ * g_task_return_new_error:
+ * @task: a #GTask.
+ * @domain: a #GQuark.
+ * @code: an error code.
+ * @format: a string with format characters.
+ * @...: a list of values to insert into @format.
+ *
+ * Sets @task's result to a new #GError created from @domain, @code,
+ * @format, and the remaining arguments, and completes the task (see
+ * g_task_return_pointer() for more discussion of exactly what this
+ * means).
+ *
+ * See also g_task_return_error().
+ *
+ * Since: 2.36
+ */
+void
+g_task_return_new_error (GTask           *task,
+                         GQuark           domain,
+                         gint             code,
+                         const char      *format,
+                         ...)
+{
+  GError *error;
+  va_list args;
+
+  va_start (args, format);
+  error = g_error_new_valist (domain, code, format, args);
+  va_end (args);
+
+  g_task_return_error (task, error);
+}
+
+/**
+ * g_task_return_error_if_cancelled:
+ * @task: a #GTask
+ *
+ * Checks if @task's #GCancellable has been cancelled, and if so, sets
+ * @task's error accordingly and completes the task (see
+ * g_task_return_pointer() for more discussion of exactly what this
+ * means).
+ *
+ * Return value: %TRUE if @task has been cancelled, %FALSE if not
+ *
+ * Since: 2.36
+ */
+gboolean
+g_task_return_error_if_cancelled (GTask *task)
+{
+  GError *error = NULL;
+
+  g_return_val_if_fail (task->result_set == FALSE, FALSE);
+
+  if (g_cancellable_set_error_if_cancelled (task->cancellable, &error))
+    {
+      /* We explicitly set task->error so this works even when
+       * check-cancellable is not set.
+       */
+      g_clear_error (&task->error);
+      task->error = error;
+
+      g_task_return (task, G_TASK_RETURN_ERROR);
+      return TRUE;
+    }
+  else
+    return FALSE;
+}
+
+/**
+ * g_task_had_error:
+ * @task: a #GTask.
+ *
+ * Tests if @task resulted in an error.
+ *
+ * Returns: %TRUE if the task resulted in an error, %FALSE otherwise.
+ *
+ * Since: 2.36
+ */
+gboolean
+g_task_had_error (GTask *task)
+{
+  if (task->error != NULL)
+    return TRUE;
+
+  if (task->check_cancellable && g_cancellable_is_cancelled (task->cancellable))
+    return TRUE;
+
+  return FALSE;
+}
+
+/**
+ * g_task_is_valid:
+ * @result: (type Gio.AsyncResult): A #GAsyncResult
+ * @source_object: (allow-none): the source object expected to be
+ *   associated with the task
+ *
+ * Checks that @result is a #GTask, and that @source_object is its
+ * source object (or that @source_object is %NULL and @result has no
+ * source object). This can be used in g_return_if_fail() checks.
+ *
+ * Return value: %TRUE if @result and @source_object are valid, %FALSE
+ * if not
+ *
+ * Since: 2.36
+ */
+gboolean
+g_task_is_valid (gpointer result,
+                 gpointer source_object)
+{
+  if (!G_IS_TASK (result))
+    return FALSE;
+
+  return G_TASK (result)->source_object == source_object;
+}
+
+static gint
+g_task_compare_priority (gconstpointer a,
+                         gconstpointer b,
+                         gpointer      user_data)
+{
+  const GTask *ta = a;
+  const GTask *tb = b;
+  gboolean a_cancelled, b_cancelled;
+
+  a_cancelled = (ta->check_cancellable &&
+                 g_cancellable_is_cancelled (ta->cancellable));
+  b_cancelled = (tb->check_cancellable &&
+                 g_cancellable_is_cancelled (tb->cancellable));
+
+  /* Let already-cancelled tasks finish right away */
+  if (a_cancelled && !b_cancelled)
+    return -1;
+  else if (b_cancelled && !a_cancelled)
+    return 1;
+
+  /* Lower priority == run sooner == negative return value */
+  return ta->priority - tb->priority;
+}
+
+static void
+g_task_thread_pool_init (void)
+{
+  task_pool = g_thread_pool_new (g_task_thread_pool_thread, NULL,
+                                 10, FALSE, NULL);
+  g_assert (task_pool != NULL);
+
+  g_thread_pool_set_sort_function (task_pool, g_task_compare_priority, NULL);
+}
+
+static void
+g_task_thread_pool_resort (void)
+{
+  g_thread_pool_set_sort_function (task_pool, g_task_compare_priority, NULL);
+}
+
+static void
+g_task_class_init (GTaskClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->finalize = g_task_finalize;
+}
+
+static gpointer
+g_task_get_user_data (GAsyncResult *res)
+{
+  return G_TASK (res)->callback_data;
+}
+
+static gboolean
+g_task_is_tagged (GAsyncResult *res,
+                  gpointer      source_tag)
+{
+  return G_TASK (res)->source_tag == source_tag;
+}
+
+static void
+g_task_async_result_iface_init (GAsyncResultIface *iface)
+{
+  iface->get_user_data = g_task_get_user_data;
+  iface->get_source_object = g_task_ref_source_object;
+  iface->is_tagged = g_task_is_tagged;
+}
diff --git a/gio/gtask.h b/gio/gtask.h
new file mode 100644
index 0000000..2cc5f65
--- /dev/null
+++ b/gio/gtask.h
@@ -0,0 +1,160 @@
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright 2011 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#if !defined (__GIO_GIO_H_INSIDE__) && !defined (GIO_COMPILATION)
+#error "Only <gio/gio.h> can be included directly."
+#endif
+
+#ifndef __G_TASK_H__
+#define __G_TASK_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_TASK         (g_task_get_type ())
+#define G_TASK(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_TASK, GTask))
+#define G_TASK_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_TASK, GTaskClass))
+#define G_IS_TASK(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_TASK))
+#define G_IS_TASK_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_TASK))
+#define G_TASK_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_TASK, GTaskClass))
+
+typedef struct _GTaskClass   GTaskClass;
+
+GLIB_AVAILABLE_IN_2_36
+GType         g_task_get_type              (void) G_GNUC_CONST;
+
+GLIB_AVAILABLE_IN_2_36
+GTask        *g_task_new                   (gpointer             source_object,
+                                            GCancellable        *cancellable,
+                                            GAsyncReadyCallback  callback,
+                                            gpointer             callback_data);
+
+GLIB_AVAILABLE_IN_2_36
+void          g_task_report_error          (gpointer             source_object,
+                                            GAsyncReadyCallback  callback,
+                                            gpointer             callback_data,
+                                            gpointer             source_tag,
+                                            GError              *error);
+GLIB_AVAILABLE_IN_2_36
+void          g_task_report_new_error      (gpointer             source_object,
+                                            GAsyncReadyCallback  callback,
+                                            gpointer             callback_data,
+                                            gpointer             source_tag,
+                                            GQuark               domain,
+                                            gint                 code,
+                                            const char          *format,
+                                            ...);
+
+GLIB_AVAILABLE_IN_2_36
+void          g_task_set_task_data         (GTask               *task,
+                                            gpointer             task_data,
+                                            GDestroyNotify       task_data_destroy);
+GLIB_AVAILABLE_IN_2_36
+void          g_task_set_priority          (GTask               *task,
+                                            gint                 priority);
+GLIB_AVAILABLE_IN_2_36
+void          g_task_set_check_cancellable (GTask               *task,
+                                            gboolean             check_cancellable);
+GLIB_AVAILABLE_IN_2_36
+void          g_task_set_source_tag        (GTask               *task,
+                                            gpointer             source_tag);
+
+GLIB_AVAILABLE_IN_2_36
+gpointer      g_task_get_source_object     (GTask               *task);
+GLIB_AVAILABLE_IN_2_36
+gpointer      g_task_get_task_data         (GTask               *task);
+GLIB_AVAILABLE_IN_2_36
+gint          g_task_get_priority          (GTask               *task);
+GLIB_AVAILABLE_IN_2_36
+GMainContext *g_task_get_context           (GTask               *task);
+GLIB_AVAILABLE_IN_2_36
+GCancellable *g_task_get_cancellable       (GTask               *task);
+GLIB_AVAILABLE_IN_2_36
+gboolean      g_task_get_check_cancellable (GTask               *task);
+GLIB_AVAILABLE_IN_2_36
+gpointer      g_task_get_source_tag        (GTask               *task);
+
+GLIB_AVAILABLE_IN_2_36
+gboolean      g_task_is_valid              (gpointer             result,
+                                            gpointer             source_object);
+
+
+typedef void (*GTaskThreadFunc)           (GTask           *task,
+                                           gpointer         source_object,
+                                           gpointer         task_data,
+                                           GCancellable    *cancellable);
+GLIB_AVAILABLE_IN_2_36
+void          g_task_run_in_thread        (GTask           *task,
+                                           GTaskThreadFunc  task_func);
+GLIB_AVAILABLE_IN_2_36
+void          g_task_run_in_thread_sync   (GTask           *task,
+                                           GTaskThreadFunc  task_func);
+GLIB_AVAILABLE_IN_2_36
+gboolean      g_task_set_return_on_cancel (GTask           *task,
+                                           gboolean         return_on_cancel);
+GLIB_AVAILABLE_IN_2_36
+gboolean      g_task_get_return_on_cancel (GTask           *task);
+
+GLIB_AVAILABLE_IN_2_36
+void          g_task_attach_source        (GTask           *task,
+                                           GSource         *source,
+                                           GSourceFunc      callback);
+
+
+GLIB_AVAILABLE_IN_2_36
+void          g_task_return_pointer            (GTask           *task,
+                                                gpointer         result,
+                                                GDestroyNotify   result_destroy);
+GLIB_AVAILABLE_IN_2_36
+void          g_task_return_boolean            (GTask           *task,
+                                                gboolean         result);
+GLIB_AVAILABLE_IN_2_36
+void          g_task_return_int                (GTask           *task,
+                                                gssize           result);
+
+GLIB_AVAILABLE_IN_2_36
+void          g_task_return_error              (GTask           *task,
+                                                GError          *error);
+GLIB_AVAILABLE_IN_2_36
+void          g_task_return_new_error          (GTask           *task,
+                                                GQuark           domain,
+                                                gint             code,
+                                                const char      *format,
+                                                ...) G_GNUC_PRINTF (4, 5);
+
+GLIB_AVAILABLE_IN_2_36
+gboolean      g_task_return_error_if_cancelled (GTask           *task);
+
+GLIB_AVAILABLE_IN_2_36
+gpointer      g_task_propagate_pointer         (GTask           *task,
+                                                GError         **error);
+GLIB_AVAILABLE_IN_2_36
+gboolean      g_task_propagate_boolean         (GTask           *task,
+                                                GError         **error);
+GLIB_AVAILABLE_IN_2_36
+gssize        g_task_propagate_int             (GTask           *task,
+                                                GError         **error);
+GLIB_AVAILABLE_IN_2_36
+gboolean      g_task_had_error                 (GTask           *task);
+
+G_END_DECLS
+
+#endif /* __G_TASK_H__ */
diff --git a/gio/tests/.gitignore b/gio/tests/.gitignore
index ccb224a..1016851 100644
--- a/gio/tests/.gitignore
+++ b/gio/tests/.gitignore
@@ -96,6 +96,7 @@ socket
 socket-client
 socket-server
 srvtarget
+task
 test.mo
 test.gresource
 test_resources.c
diff --git a/gio/tests/Makefile.am b/gio/tests/Makefile.am
index 3866b13..5a1fdc5 100644
--- a/gio/tests/Makefile.am
+++ b/gio/tests/Makefile.am
@@ -62,6 +62,7 @@ TEST_PROGS +=	 		\
 	proxy-test		\
 	inet-address		\
 	permission		\
+	task			\
 	$(NULL)
 
 if HAVE_DBUS_DAEMON
@@ -469,6 +470,9 @@ inet_address_LDADD   = $(progs_ldadd)
 permission_SOURCES = permission.c
 permission_LDADD   = $(progs_ldadd)
 
+task_SOURCES = task.c
+task_LDADD   = $(progs_ldadd)
+
 schema_tests = \
 	schema-tests/array-default-not-in-choices.gschema.xml		\
 	schema-tests/bad-choice.gschema.xml				\
diff --git a/gio/tests/task.c b/gio/tests/task.c
new file mode 100644
index 0000000..7c7606f
--- /dev/null
+++ b/gio/tests/task.c
@@ -0,0 +1,1668 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ */
+
+#include <gio/gio.h>
+
+static GMainLoop *loop;
+static GThread *main_thread;
+static gssize magic;
+
+/* We need objects for a few tests where we don't care what type
+ * they are, just that they're GObjects.
+ */
+#define g_dummy_object_new g_socket_client_new
+
+/* test_basic */
+
+static void
+basic_callback (GObject      *object,
+                GAsyncResult *result,
+                gpointer      user_data)
+{
+  gssize *result_out = user_data;
+  GError *error = NULL;
+
+  g_assert (object == NULL);
+  g_assert (g_task_is_valid (result, object));
+  g_assert (g_async_result_get_user_data (result) == user_data);
+  g_assert (!g_task_had_error (G_TASK (result)));
+
+  *result_out = g_task_propagate_int (G_TASK (result), &error);
+  g_assert_no_error (error);
+
+  g_main_loop_quit (loop);
+}
+
+static gboolean
+basic_return (gpointer user_data)
+{
+  GTask *task = user_data;
+
+  g_task_return_int (task, magic);
+  g_object_unref (task);
+
+  return FALSE;
+}
+
+static void
+basic_destroy_notify (gpointer user_data)
+{
+  gboolean *destroyed = user_data;
+
+  *destroyed = TRUE;
+}
+
+static void
+test_basic (void)
+{
+  GTask *task;
+  gssize result;
+  gboolean task_data_destroyed = FALSE;
+
+  task = g_task_new (NULL, NULL, basic_callback, &result);
+  g_task_set_task_data (task, &task_data_destroyed, basic_destroy_notify);
+  g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
+
+  g_idle_add (basic_return, task);
+  g_main_loop_run (loop);
+
+  g_assert_cmpint (result, ==, magic);
+  g_assert (task_data_destroyed == TRUE);
+  g_assert (task == NULL);
+}
+
+/* test_error */
+
+static void
+error_callback (GObject      *object,
+                GAsyncResult *result,
+                gpointer      user_data)
+{
+  gssize *result_out = user_data;
+  GError *error = NULL;
+
+  g_assert (object == NULL);
+  g_assert (g_task_is_valid (result, object));
+  g_assert (g_async_result_get_user_data (result) == user_data);
+  g_assert (g_task_had_error (G_TASK (result)));
+
+  *result_out = g_task_propagate_int (G_TASK (result), &error);
+  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED);
+  g_error_free (error);
+
+  g_main_loop_quit (loop);
+}
+
+static gboolean
+error_return (gpointer user_data)
+{
+  GTask *task = user_data;
+
+  g_task_return_new_error (task,
+                           G_IO_ERROR, G_IO_ERROR_FAILED,
+                           "Failed");
+  g_object_unref (task);
+
+  return FALSE;
+}
+
+static void
+error_destroy_notify (gpointer user_data)
+{
+  gboolean *destroyed = user_data;
+
+  *destroyed = TRUE;
+}
+
+static void
+test_error (void)
+{
+  GTask *task;
+  gssize result;
+  gboolean first_task_data_destroyed = FALSE;
+  gboolean second_task_data_destroyed = FALSE;
+
+  task = g_task_new (NULL, NULL, error_callback, &result);
+  g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
+
+  g_assert (first_task_data_destroyed == FALSE);
+  g_task_set_task_data (task, &first_task_data_destroyed, error_destroy_notify);
+  g_assert (first_task_data_destroyed == FALSE);
+
+  /* Calling g_task_set_task_data() again will destroy the first data */
+  g_task_set_task_data (task, &second_task_data_destroyed, error_destroy_notify);
+  g_assert (first_task_data_destroyed == TRUE);
+  g_assert (second_task_data_destroyed == FALSE);
+
+  g_idle_add (error_return, task);
+  g_main_loop_run (loop);
+
+  g_assert_cmpint (result, ==, -1);
+  g_assert (second_task_data_destroyed == TRUE);
+  g_assert (task == NULL);
+}
+
+/* test_return_from_same_iteration: calling g_task_return_* from the
+ * loop iteration the task was created in defers completion until the
+ * next iteration.
+ */
+gboolean same_result = FALSE;
+
+static void
+same_callback (GObject      *object,
+               GAsyncResult *result,
+               gpointer      user_data)
+{
+  gboolean *result_out = user_data;
+  GError *error = NULL;
+
+  g_assert (object == NULL);
+  g_assert (g_task_is_valid (result, object));
+  g_assert (g_async_result_get_user_data (result) == user_data);
+  g_assert (!g_task_had_error (G_TASK (result)));
+
+  *result_out = g_task_propagate_boolean (G_TASK (result), &error);
+  g_assert_no_error (error);
+
+  g_main_loop_quit (loop);
+}
+
+static gboolean
+same_start (gpointer user_data)
+{
+  gpointer *weak_pointer = user_data;
+  GTask *task;
+
+  task = g_task_new (NULL, NULL, same_callback, &same_result);
+  *weak_pointer = task;
+  g_object_add_weak_pointer (G_OBJECT (task), weak_pointer);
+
+  g_task_return_boolean (task, TRUE);
+  g_object_unref (task);
+
+  /* same_callback should not have been invoked yet */
+  g_assert (same_result == FALSE);
+  g_assert (*weak_pointer == task);
+
+  return FALSE;
+}
+
+static void
+test_return_from_same_iteration (void)
+{
+  gpointer weak_pointer;
+
+  g_idle_add (same_start, &weak_pointer);
+  g_main_loop_run (loop);
+
+  g_assert (same_result == TRUE);
+  g_assert (weak_pointer == NULL);
+}
+
+/* test_return_from_toplevel: calling g_task_return_* from outside any
+ * main loop completes the task inside the main loop.
+ */
+
+static void
+toplevel_callback (GObject      *object,
+              GAsyncResult *result,
+              gpointer      user_data)
+{
+  gboolean *result_out = user_data;
+  GError *error = NULL;
+
+  g_assert (object == NULL);
+  g_assert (g_task_is_valid (result, object));
+  g_assert (g_async_result_get_user_data (result) == user_data);
+  g_assert (!g_task_had_error (G_TASK (result)));
+
+  *result_out = g_task_propagate_boolean (G_TASK (result), &error);
+  g_assert_no_error (error);
+
+  g_main_loop_quit (loop);
+}
+
+static void
+test_return_from_toplevel (void)
+{
+  GTask *task;
+  gboolean result = FALSE;
+
+  task = g_task_new (NULL, NULL, toplevel_callback, &result);
+  g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
+
+  g_task_return_boolean (task, TRUE);
+  g_object_unref (task);
+
+  /* toplevel_callback should not have been invoked yet */
+  g_assert (result == FALSE);
+  g_assert (task != NULL);
+
+  g_main_loop_run (loop);
+
+  g_assert (result == TRUE);
+  g_assert (task == NULL);
+}
+
+/* test_return_from_anon_thread: calling g_task_return_* from a
+ * thread with no thread-default main context will complete the
+ * task in the task's context/thread.
+ */
+
+GThread *anon_thread;
+
+static void
+anon_callback (GObject      *object,
+               GAsyncResult *result,
+               gpointer      user_data)
+{
+  gssize *result_out = user_data;
+  GError *error = NULL;
+
+  g_assert (object == NULL);
+  g_assert (g_task_is_valid (result, object));
+  g_assert (g_async_result_get_user_data (result) == user_data);
+  g_assert (!g_task_had_error (G_TASK (result)));
+
+  g_assert (g_thread_self () == main_thread);
+
+  *result_out = g_task_propagate_int (G_TASK (result), &error);
+  g_assert_no_error (error);
+
+  g_main_loop_quit (loop);
+}
+
+static gpointer
+anon_thread_func (gpointer user_data)
+{
+  GTask *task = user_data;
+
+  g_task_return_int (task, magic);
+  g_object_unref (task);
+
+  return NULL;
+}
+
+static gboolean
+anon_start (gpointer user_data)
+{
+  GTask *task = user_data;
+
+  anon_thread = g_thread_new ("test_return_from_anon_thread",
+                              anon_thread_func, task);
+  return FALSE;
+}
+
+static void
+test_return_from_anon_thread (void)
+{
+  GTask *task;
+  gssize result = 0;
+
+  task = g_task_new (NULL, NULL, anon_callback, &result);
+  g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
+
+  g_idle_add (anon_start, task);
+  g_main_loop_run (loop);
+
+  g_thread_join (anon_thread);
+
+  g_assert_cmpint (result, ==, magic);
+  g_assert (task == NULL);
+}
+
+/* test_return_from_wrong_thread: calling g_task_return_* from a
+ * thread with its own thread-default main context will complete the
+ * task in the task's context/thread.
+ */
+
+GThread *wrong_thread;
+
+static void
+wrong_callback (GObject      *object,
+               GAsyncResult *result,
+               gpointer      user_data)
+{
+  gssize *result_out = user_data;
+  GError *error = NULL;
+
+  g_assert (object == NULL);
+  g_assert (g_task_is_valid (result, object));
+  g_assert (g_async_result_get_user_data (result) == user_data);
+  g_assert (!g_task_had_error (G_TASK (result)));
+
+  g_assert (g_thread_self () == main_thread);
+
+  *result_out = g_task_propagate_int (G_TASK (result), &error);
+  g_assert_no_error (error);
+
+  g_main_loop_quit (loop);
+}
+
+static gpointer
+wrong_thread_func (gpointer user_data)
+{
+  GTask *task = user_data;
+  GMainContext *context;
+
+  context = g_main_context_new ();
+  g_main_context_push_thread_default (context);
+
+  g_assert (g_task_get_context (task) != context);
+
+  g_task_return_int (task, magic);
+  g_object_unref (task);
+
+  g_main_context_pop_thread_default (context);
+  g_main_context_unref (context);
+
+  return NULL;
+}
+
+static gboolean
+wrong_start (gpointer user_data)
+{
+  GTask *task = user_data;
+
+  wrong_thread = g_thread_new ("test_return_from_anon_thread",
+                               wrong_thread_func, task);
+  return FALSE;
+}
+
+static void
+test_return_from_wrong_thread (void)
+{
+  GTask *task;
+  gssize result = 0;
+
+  task = g_task_new (NULL, NULL, wrong_callback, &result);
+  g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
+
+  g_idle_add (wrong_start, task);
+  g_main_loop_run (loop);
+
+  g_thread_join (wrong_thread);
+
+  g_assert_cmpint (result, ==, magic);
+  g_assert (task == NULL);
+}
+
+/* test_no_callback */
+
+static void
+test_no_callback (void)
+{
+  GTask *task;
+
+  task = g_task_new (NULL, NULL, NULL, NULL);
+  g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
+
+  g_task_return_boolean (task, TRUE);
+  g_object_unref (task);
+
+  /* Since there's no callback, g_task_return_boolean() will
+   * not have queued an idle source and taken a ref on task,
+   * so we just dropped the last ref.
+   */
+  g_assert (task == NULL);
+}
+
+/* test_report_error */
+
+static void test_report_error (void);
+
+static void
+report_callback (GObject      *object,
+                 GAsyncResult *result,
+                 gpointer      user_data)
+{
+  gpointer *weak_pointer = user_data;
+  GError *error = NULL;
+  gssize ret;
+
+  g_assert (object == NULL);
+  g_assert (g_task_is_valid (result, object));
+  g_assert (g_async_result_get_user_data (result) == user_data);
+  g_assert (g_async_result_is_tagged (result, test_report_error));
+  g_assert (g_task_get_source_tag (G_TASK (result)) == test_report_error);
+  g_assert (g_task_had_error (G_TASK (result)));
+
+  ret = g_task_propagate_int (G_TASK (result), &error);
+  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED);
+  g_assert_cmpint (ret, ==, -1);
+  g_error_free (error);
+
+  *weak_pointer = result;
+  g_object_add_weak_pointer (G_OBJECT (result), weak_pointer);
+
+  g_main_loop_quit (loop);
+}
+
+static void
+test_report_error (void)
+{
+  gpointer weak_pointer = (gpointer)-1;
+
+  g_task_report_new_error (NULL, report_callback, &weak_pointer,
+                           test_report_error,
+                           G_IO_ERROR, G_IO_ERROR_FAILED,
+                           "Failed");
+  g_main_loop_run (loop);
+
+  g_assert (weak_pointer == NULL);
+}
+
+/* test_priority: tasks complete in priority order */
+
+static int counter = 0;
+
+static void
+priority_callback (GObject      *object,
+                   GAsyncResult *result,
+                   gpointer      user_data)
+{
+  gssize *ret_out = user_data;
+  GError *error = NULL;
+
+  g_assert (object == NULL);
+  g_assert (g_task_is_valid (result, object));
+  g_assert (g_async_result_get_user_data (result) == user_data);
+  g_assert (!g_task_had_error (G_TASK (result)));
+
+  g_task_propagate_boolean (G_TASK (result), &error);
+  g_assert_no_error (error);
+
+  *ret_out = ++counter;
+
+  if (counter == 3)
+    g_main_loop_quit (loop);
+}
+
+static void
+test_priority (void)
+{
+  GTask *t1, *t2, *t3;
+  gssize ret1, ret2, ret3;
+
+  /* t2 has higher priority than either t1 or t3, so we can't
+   * accidentally pass the test just by completing the tasks in the
+   * order they were created (or in reverse order).
+   */
+
+  t1 = g_task_new (NULL, NULL, priority_callback, &ret1);
+  g_task_set_priority (t1, G_PRIORITY_DEFAULT);
+  g_task_return_boolean (t1, TRUE);
+  g_object_unref (t1);
+
+  t2 = g_task_new (NULL, NULL, priority_callback, &ret2);
+  g_task_set_priority (t2, G_PRIORITY_HIGH);
+  g_task_return_boolean (t2, TRUE);
+  g_object_unref (t2);
+
+  t3 = g_task_new (NULL, NULL, priority_callback, &ret3);
+  g_task_set_priority (t3, G_PRIORITY_LOW);
+  g_task_return_boolean (t3, TRUE);
+  g_object_unref (t3);
+
+  g_main_loop_run (loop);
+
+  g_assert_cmpint (ret2, ==, 1);
+  g_assert_cmpint (ret1, ==, 2);
+  g_assert_cmpint (ret3, ==, 3);
+}
+
+/* test_check_cancellable: cancellation overrides return value */
+
+enum {
+  CANCEL_BEFORE     = (1 << 1),
+  CANCEL_AFTER      = (1 << 2),
+  CHECK_CANCELLABLE = (1 << 3)
+};
+#define NUM_CANCEL_TESTS (CANCEL_BEFORE | CANCEL_AFTER | CHECK_CANCELLABLE)
+
+static void
+cancel_callback (GObject      *object,
+                 GAsyncResult *result,
+                 gpointer      user_data)
+{
+  int state = GPOINTER_TO_INT (user_data);
+  GTask *task;
+  GCancellable *cancellable;
+  GError *error = NULL;
+
+  g_assert (object == NULL);
+  g_assert (g_task_is_valid (result, object));
+  g_assert (g_async_result_get_user_data (result) == user_data);
+
+  task = G_TASK (result);
+  cancellable = g_task_get_cancellable (task);
+  g_assert (G_IS_CANCELLABLE (cancellable));
+
+  if (state & (CANCEL_BEFORE | CANCEL_AFTER))
+    g_assert (g_cancellable_is_cancelled (cancellable));
+  else
+    g_assert (!g_cancellable_is_cancelled (cancellable));
+
+  if (state & CHECK_CANCELLABLE)
+    g_assert (g_task_get_check_cancellable (task));
+  else
+    g_assert (!g_task_get_check_cancellable (task));
+
+  if (g_task_propagate_boolean (task, &error))
+    {
+      g_assert (!g_cancellable_is_cancelled (cancellable) ||
+                !g_task_get_check_cancellable (task));
+    }
+  else
+    {
+      g_assert (g_cancellable_is_cancelled (cancellable) &&
+                g_task_get_check_cancellable (task));
+      g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+      g_error_free (error);
+    }
+
+  g_main_loop_quit (loop);
+}
+
+static void
+test_check_cancellable (void)
+{
+  GTask *task;
+  GCancellable *cancellable;
+  int state;
+
+  cancellable = g_cancellable_new ();
+
+  for (state = 0; state <= NUM_CANCEL_TESTS; state++)
+    {
+      task = g_task_new (NULL, cancellable, cancel_callback,
+                         GINT_TO_POINTER (state));
+      g_task_set_check_cancellable (task, (state & CHECK_CANCELLABLE) != 0);
+
+      if (state & CANCEL_BEFORE)
+        g_cancellable_cancel (cancellable);
+      g_task_return_boolean (task, TRUE);
+      if (state & CANCEL_AFTER)
+        g_cancellable_cancel (cancellable);
+
+      g_main_loop_run (loop);
+      g_object_unref (task);
+      g_cancellable_reset (cancellable);
+    }
+
+  g_object_unref (cancellable);
+}
+
+/* test_return_if_cancelled */
+
+static void
+return_if_cancelled_callback (GObject      *object,
+                              GAsyncResult *result,
+                              gpointer      user_data)
+{
+  GError *error = NULL;
+
+  g_assert (object == NULL);
+  g_assert (g_task_is_valid (result, object));
+  g_assert (g_async_result_get_user_data (result) == user_data);
+  g_assert (g_task_had_error (G_TASK (result)));
+
+  g_task_propagate_boolean (G_TASK (result), &error);
+  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+  g_clear_error (&error);
+
+  g_main_loop_quit (loop);
+}
+
+static void
+test_return_if_cancelled (void)
+{
+  GTask *task;
+  GCancellable *cancellable;
+  gboolean cancelled;
+
+  cancellable = g_cancellable_new ();
+
+  task = g_task_new (NULL, cancellable, return_if_cancelled_callback, NULL);
+  g_cancellable_cancel (cancellable);
+  cancelled = g_task_return_error_if_cancelled (task);
+  g_assert (cancelled);
+  g_main_loop_run (loop);
+  g_object_unref (task);
+  g_cancellable_reset (cancellable);
+
+  task = g_task_new (NULL, cancellable, return_if_cancelled_callback, NULL);
+  g_task_set_check_cancellable (task, FALSE);
+  g_cancellable_cancel (cancellable);
+  cancelled = g_task_return_error_if_cancelled (task);
+  g_assert (cancelled);
+  g_main_loop_run (loop);
+  g_object_unref (task);
+  g_object_unref (cancellable);
+}
+
+/* test_run_in_thread */
+
+static void
+run_in_thread_callback (GObject      *object,
+                        GAsyncResult *result,
+                        gpointer      user_data)
+{
+  gboolean *done = user_data;
+  GError *error = NULL;
+  gssize ret;
+
+  g_assert (g_thread_self () == main_thread);
+
+  g_assert (object == NULL);
+  g_assert (g_task_is_valid (result, object));
+  g_assert (g_async_result_get_user_data (result) == user_data);
+  g_assert (!g_task_had_error (G_TASK (result)));
+
+  ret = g_task_propagate_int (G_TASK (result), &error);
+  g_assert_no_error (error);
+  g_assert_cmpint (ret, ==, magic);
+
+  *done = TRUE;
+  g_main_loop_quit (loop);
+}
+
+static void
+run_in_thread_thread (GTask        *task,
+                      gpointer      source_object,
+                      gpointer      task_data,
+                      GCancellable *cancellable)
+{
+  gboolean *thread_ran = task_data;
+
+  g_assert (source_object == g_task_get_source_object (task));
+  g_assert (task_data == g_task_get_task_data (task));
+  g_assert (cancellable == g_task_get_cancellable (task));
+
+  g_assert (g_thread_self () != main_thread);
+
+  *thread_ran = TRUE;
+  g_task_return_int (task, magic);
+}
+
+static void
+test_run_in_thread (void)
+{
+  GTask *task;
+  volatile gboolean thread_ran = FALSE;
+  gboolean done = FALSE;
+
+  task = g_task_new (NULL, NULL, run_in_thread_callback, &done);
+  g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
+
+  g_task_set_task_data (task, (gpointer)&thread_ran, NULL);
+  g_task_run_in_thread (task, run_in_thread_thread);
+  g_object_unref (task);
+
+  while (!thread_ran)
+    g_usleep (100);
+
+  g_assert (done == FALSE);
+  g_assert (task != NULL);
+
+  g_main_loop_run (loop);
+
+  g_assert (done == TRUE);
+  g_assert (task == NULL);
+}
+
+/* test_run_in_thread_sync */
+
+static void
+run_in_thread_sync_callback (GObject      *object,
+                             GAsyncResult *result,
+                             gpointer      user_data)
+{
+  /* g_task_run_in_thread_sync() does not invoke the task's callback */
+  g_assert_not_reached ();
+}
+
+static void
+run_in_thread_sync_thread (GTask        *task,
+                           gpointer      source_object,
+                           gpointer      task_data,
+                           GCancellable *cancellable)
+{
+  gboolean *thread_ran = task_data;
+
+  g_assert (source_object == g_task_get_source_object (task));
+  g_assert (task_data == g_task_get_task_data (task));
+  g_assert (cancellable == g_task_get_cancellable (task));
+
+  g_assert (g_thread_self () != main_thread);
+
+  *thread_ran = TRUE;
+  g_task_return_int (task, magic);
+}
+
+static void
+test_run_in_thread_sync (void)
+{
+  GTask *task;
+  gboolean thread_ran = FALSE;
+  gssize ret;
+  GError *error = NULL;
+
+  task = g_task_new (NULL, NULL, run_in_thread_sync_callback, NULL);
+
+  g_task_set_task_data (task, &thread_ran, NULL);
+  g_task_run_in_thread_sync (task, run_in_thread_sync_thread);
+
+  g_assert (thread_ran == TRUE);
+  g_assert (task != NULL);
+  g_assert (!g_task_had_error (task));
+
+  ret = g_task_propagate_int (task, &error);
+  g_assert_no_error (error);
+  g_assert_cmpint (ret, ==, magic);
+
+  g_object_unref (task);
+}
+
+/* test_run_in_thread_priority */
+
+static GMutex fake_task_mutex, last_fake_task_mutex;
+static gint sequence_number = 0;
+
+static void
+quit_main_loop_callback (GObject      *object,
+                         GAsyncResult *result,
+                         gpointer      user_data)
+{
+  GError *error = NULL;
+  gboolean ret;
+
+  g_assert (g_thread_self () == main_thread);
+
+  g_assert (object == NULL);
+  g_assert (g_task_is_valid (result, object));
+  g_assert (g_async_result_get_user_data (result) == user_data);
+  g_assert (!g_task_had_error (G_TASK (result)));
+
+  ret = g_task_propagate_boolean (G_TASK (result), &error);
+  g_assert_no_error (error);
+  g_assert_cmpint (ret, ==, TRUE);
+
+  g_main_loop_quit (loop);
+}
+
+static void
+set_sequence_number_thread (GTask        *task,
+                            gpointer      source_object,
+                            gpointer      task_data,
+                            GCancellable *cancellable)
+{
+  gint *seq_no_p = task_data;
+
+  *seq_no_p = ++sequence_number;
+  g_task_return_boolean (task, TRUE);
+}
+
+static void
+fake_task_thread (GTask        *task,
+                  gpointer      source_object,
+                  gpointer      task_data,
+                  GCancellable *cancellable)
+{
+  GMutex *mutex = task_data;
+
+  g_mutex_lock (mutex);
+  g_mutex_unlock (mutex);
+  g_task_return_boolean (task, TRUE);
+}
+
+#define G_TASK_THREAD_POOL_SIZE 10
+
+static void
+test_run_in_thread_priority (void)
+{
+  GTask *task;
+  GCancellable *cancellable;
+  int seq_a, seq_b, seq_c, seq_d;
+  int i;
+
+  /* Flush the thread pool, then clog it up with junk tasks */
+  g_thread_pool_stop_unused_threads ();
+
+  g_mutex_lock (&fake_task_mutex);
+  for (i = 0; i < G_TASK_THREAD_POOL_SIZE - 1; i++)
+    {
+      task = g_task_new (NULL, NULL, NULL, NULL);
+      g_task_set_task_data (task, &fake_task_mutex, NULL);
+      g_assert_cmpint (g_task_get_priority (task), ==, G_PRIORITY_DEFAULT);
+      g_task_set_priority (task, G_PRIORITY_HIGH * 2);
+      g_assert_cmpint (g_task_get_priority (task), ==, G_PRIORITY_HIGH * 2);
+      g_task_run_in_thread (task, fake_task_thread);
+      g_object_unref (task);
+    }
+
+  g_mutex_lock (&last_fake_task_mutex);
+  task = g_task_new (NULL, NULL, NULL, NULL);
+  g_task_set_task_data (task, &last_fake_task_mutex, NULL);
+  g_task_set_priority (task, G_PRIORITY_HIGH * 2);
+  g_task_run_in_thread (task, fake_task_thread);
+  g_object_unref (task);
+
+  /* Queue three more tasks that we'll arrange to have run serially */
+  task = g_task_new (NULL, NULL, NULL, NULL);
+  g_task_set_task_data (task, &seq_a, NULL);
+  g_task_run_in_thread (task, set_sequence_number_thread);
+  g_object_unref (task);
+  
+  task = g_task_new (NULL, NULL, quit_main_loop_callback, NULL);
+  g_task_set_task_data (task, &seq_b, NULL);
+  g_task_set_priority (task, G_PRIORITY_LOW);
+  g_task_run_in_thread (task, set_sequence_number_thread);
+  g_object_unref (task);
+  
+  task = g_task_new (NULL, NULL, NULL, NULL);
+  g_task_set_task_data (task, &seq_c, NULL);
+  g_task_set_priority (task, G_PRIORITY_HIGH);
+  g_task_run_in_thread (task, set_sequence_number_thread);
+  g_object_unref (task);
+  
+  cancellable = g_cancellable_new ();
+  task = g_task_new (NULL, cancellable, NULL, NULL);
+  g_task_set_task_data (task, &seq_d, NULL);
+  g_task_run_in_thread (task, set_sequence_number_thread);
+  g_cancellable_cancel (cancellable);
+  g_object_unref (cancellable);
+  g_object_unref (task);
+
+  /* Let the last fake task complete; the four other tasks will then
+   * complete serially, in the order D, C, A, B, and B will quit the
+   * main loop.
+   */
+  g_mutex_unlock (&last_fake_task_mutex);
+  g_main_loop_run (loop);
+
+  g_assert_cmpint (seq_d, ==, 1);
+  g_assert_cmpint (seq_c, ==, 2);
+  g_assert_cmpint (seq_a, ==, 3);
+  g_assert_cmpint (seq_b, ==, 4);
+
+  g_mutex_unlock (&fake_task_mutex);
+}
+
+/* test_return_on_cancel */
+
+GMutex roc_init_mutex, roc_finish_mutex;
+GCond roc_init_cond, roc_finish_cond;
+
+typedef enum {
+  THREAD_STARTING,
+  THREAD_RUNNING,
+  THREAD_CANCELLED,
+  THREAD_COMPLETED
+} ThreadState;
+
+static void
+return_on_cancel_callback (GObject      *object,
+                           GAsyncResult *result,
+                           gpointer      user_data)
+{
+  gboolean *callback_ran = user_data;
+  GError *error = NULL;
+  gssize ret;
+
+  g_assert (g_thread_self () == main_thread);
+
+  g_assert (object == NULL);
+  g_assert (g_task_is_valid (result, object));
+  g_assert (g_async_result_get_user_data (result) == user_data);
+  g_assert (g_task_had_error (G_TASK (result)));
+
+  ret = g_task_propagate_int (G_TASK (result), &error);
+  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+  g_clear_error (&error);
+  g_assert_cmpint (ret, ==, -1);
+
+  *callback_ran = TRUE;
+  g_main_loop_quit (loop);
+}
+
+static void
+return_on_cancel_thread (GTask        *task,
+                         gpointer      source_object,
+                         gpointer      task_data,
+                         GCancellable *cancellable)
+{
+  ThreadState *state = task_data;
+
+  g_assert (source_object == g_task_get_source_object (task));
+  g_assert (task_data == g_task_get_task_data (task));
+  g_assert (cancellable == g_task_get_cancellable (task));
+
+  g_assert (g_thread_self () != main_thread);
+
+  g_mutex_lock (&roc_init_mutex);
+  *state = THREAD_RUNNING;
+  g_cond_signal (&roc_init_cond);
+  g_mutex_unlock (&roc_init_mutex);
+
+  g_mutex_lock (&roc_finish_mutex);
+
+  if (!g_task_get_return_on_cancel (task) ||
+      g_task_set_return_on_cancel (task, FALSE))
+    {
+      *state = THREAD_COMPLETED;
+      g_task_return_int (task, magic);
+    }
+  else
+    *state = THREAD_CANCELLED;
+
+  g_cond_signal (&roc_finish_cond);
+  g_mutex_unlock (&roc_finish_mutex);
+}
+
+static void
+test_return_on_cancel (void)
+{
+  GTask *task;
+  GCancellable *cancellable;
+  volatile ThreadState thread_state;
+  gboolean callback_ran;
+
+  cancellable = g_cancellable_new ();
+
+  /* If return-on-cancel is FALSE (default), the task does not return
+   * early.
+   */
+  callback_ran = FALSE;
+  thread_state = THREAD_STARTING;
+  task = g_task_new (NULL, cancellable, return_on_cancel_callback, &callback_ran);
+
+  g_task_set_task_data (task, (gpointer)&thread_state, NULL);
+  g_mutex_lock (&roc_init_mutex);
+  g_mutex_lock (&roc_finish_mutex);
+  g_task_run_in_thread (task, return_on_cancel_thread);
+  g_object_unref (task);
+
+  while (thread_state == THREAD_STARTING)
+    g_cond_wait (&roc_init_cond, &roc_init_mutex);
+  g_mutex_unlock (&roc_init_mutex);
+
+  g_assert (thread_state == THREAD_RUNNING);
+  g_assert (callback_ran == FALSE);
+
+  g_cancellable_cancel (cancellable);
+  g_mutex_unlock (&roc_finish_mutex);
+  g_main_loop_run (loop);
+
+  g_assert (thread_state == THREAD_COMPLETED);
+  g_assert (callback_ran == TRUE);
+
+  g_cancellable_reset (cancellable);
+
+  /* If return-on-cancel is TRUE, it does return early */
+  callback_ran = FALSE;
+  thread_state = THREAD_STARTING;
+  task = g_task_new (NULL, cancellable, return_on_cancel_callback, &callback_ran);
+  g_task_set_return_on_cancel (task, TRUE);
+
+  g_task_set_task_data (task, (gpointer)&thread_state, NULL);
+  g_mutex_lock (&roc_init_mutex);
+  g_mutex_lock (&roc_finish_mutex);
+  g_task_run_in_thread (task, return_on_cancel_thread);
+  g_object_unref (task);
+
+  while (thread_state == THREAD_STARTING)
+    g_cond_wait (&roc_init_cond, &roc_init_mutex);
+  g_mutex_unlock (&roc_init_mutex);
+
+  g_assert (thread_state == THREAD_RUNNING);
+  g_assert (callback_ran == FALSE);
+
+  g_cancellable_cancel (cancellable);
+  g_main_loop_run (loop);
+  g_assert (thread_state == THREAD_RUNNING);
+  g_assert (callback_ran == TRUE);
+
+  while (thread_state == THREAD_RUNNING)
+    g_cond_wait (&roc_finish_cond, &roc_finish_mutex);
+  g_mutex_unlock (&roc_finish_mutex);
+
+  g_assert (thread_state == THREAD_CANCELLED);
+  /* We can't g_assert (task == NULL) here because it won't become NULL
+   * until a little bit after roc_finish_cond is signaled.
+   */
+
+  g_cancellable_reset (cancellable);
+
+  /* If the task is already cancelled before it starts, it returns
+   * immediately, but the thread func still runs.
+   */
+  callback_ran = FALSE;
+  thread_state = THREAD_STARTING;
+  task = g_task_new (NULL, cancellable, return_on_cancel_callback, &callback_ran);
+  g_task_set_return_on_cancel (task, TRUE);
+
+  g_cancellable_cancel (cancellable);
+
+  g_task_set_task_data (task, (gpointer)&thread_state, NULL);
+  g_mutex_lock (&roc_init_mutex);
+  g_mutex_lock (&roc_finish_mutex);
+  g_task_run_in_thread (task, return_on_cancel_thread);
+  g_object_unref (task);
+
+  g_main_loop_run (loop);
+  g_assert (callback_ran == TRUE);
+
+  while (thread_state == THREAD_STARTING)
+    g_cond_wait (&roc_init_cond, &roc_init_mutex);
+  g_mutex_unlock (&roc_init_mutex);
+
+  g_assert (thread_state == THREAD_RUNNING);
+
+  while (thread_state == THREAD_RUNNING)
+    g_cond_wait (&roc_finish_cond, &roc_finish_mutex);
+  g_mutex_unlock (&roc_finish_mutex);
+
+  g_assert (thread_state == THREAD_CANCELLED);
+
+  g_object_unref (cancellable);
+}
+
+/* test_return_on_cancel_sync */
+
+static gpointer
+cancel_sync_runner_thread (gpointer task)
+{
+  g_task_run_in_thread_sync (task, return_on_cancel_thread);
+  return NULL;
+}
+
+static void
+test_return_on_cancel_sync (void)
+{
+  GTask *task;
+  GCancellable *cancellable;
+  volatile ThreadState thread_state;
+  GThread *runner_thread;
+  gssize ret;
+  GError *error = NULL;
+
+  cancellable = g_cancellable_new ();
+
+  /* If return-on-cancel is FALSE, the task does not return early.
+   */
+  thread_state = THREAD_STARTING;
+  task = g_task_new (NULL, cancellable, run_in_thread_sync_callback, NULL);
+
+  g_task_set_task_data (task, (gpointer)&thread_state, NULL);
+  g_mutex_lock (&roc_init_mutex);
+  g_mutex_lock (&roc_finish_mutex);
+  runner_thread = g_thread_new ("return-on-cancel-sync runner thread",
+                                cancel_sync_runner_thread, task);
+
+  while (thread_state == THREAD_STARTING)
+    g_cond_wait (&roc_init_cond, &roc_init_mutex);
+  g_mutex_unlock (&roc_init_mutex);
+
+  g_assert (thread_state == THREAD_RUNNING);
+
+  g_cancellable_cancel (cancellable);
+  g_mutex_unlock (&roc_finish_mutex);
+  g_thread_join (runner_thread);
+  g_assert (thread_state == THREAD_COMPLETED);
+
+  ret = g_task_propagate_int (task, &error);
+  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+  g_clear_error (&error);
+  g_assert_cmpint (ret, ==, -1);
+
+  g_object_unref (task);
+
+  g_cancellable_reset (cancellable);
+
+  /* If return-on-cancel is TRUE, it does return early */
+  thread_state = THREAD_STARTING;
+  task = g_task_new (NULL, cancellable, run_in_thread_sync_callback, NULL);
+  g_task_set_return_on_cancel (task, TRUE);
+
+  g_task_set_task_data (task, (gpointer)&thread_state, NULL);
+  g_mutex_lock (&roc_init_mutex);
+  g_mutex_lock (&roc_finish_mutex);
+  runner_thread = g_thread_new ("return-on-cancel-sync runner thread",
+                                cancel_sync_runner_thread, task);
+
+  while (thread_state == THREAD_STARTING)
+    g_cond_wait (&roc_init_cond, &roc_init_mutex);
+  g_mutex_unlock (&roc_init_mutex);
+
+  g_assert (thread_state == THREAD_RUNNING);
+
+  g_cancellable_cancel (cancellable);
+  g_thread_join (runner_thread);
+  g_assert (thread_state == THREAD_RUNNING);
+
+  ret = g_task_propagate_int (task, &error);
+  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+  g_clear_error (&error);
+  g_assert_cmpint (ret, ==, -1);
+
+  g_object_unref (task);
+
+  while (thread_state == THREAD_RUNNING)
+    g_cond_wait (&roc_finish_cond, &roc_finish_mutex);
+  g_mutex_unlock (&roc_finish_mutex);
+
+  g_assert (thread_state == THREAD_CANCELLED);
+
+  g_cancellable_reset (cancellable);
+
+  /* If the task is already cancelled before it starts, it returns
+   * immediately, but the thread func still runs.
+   */
+  thread_state = THREAD_STARTING;
+  task = g_task_new (NULL, cancellable, run_in_thread_sync_callback, NULL);
+  g_task_set_return_on_cancel (task, TRUE);
+
+  g_cancellable_cancel (cancellable);
+
+  g_task_set_task_data (task, (gpointer)&thread_state, NULL);
+  g_mutex_lock (&roc_init_mutex);
+  g_mutex_lock (&roc_finish_mutex);
+  runner_thread = g_thread_new ("return-on-cancel-sync runner thread",
+                                cancel_sync_runner_thread, task);
+
+  g_thread_join (runner_thread);
+  g_assert (thread_state == THREAD_STARTING);
+
+  ret = g_task_propagate_int (task, &error);
+  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+  g_clear_error (&error);
+  g_assert_cmpint (ret, ==, -1);
+
+  g_object_unref (task);
+
+  while (thread_state == THREAD_STARTING)
+    g_cond_wait (&roc_init_cond, &roc_init_mutex);
+  g_mutex_unlock (&roc_init_mutex);
+
+  g_assert (thread_state == THREAD_RUNNING);
+
+  while (thread_state == THREAD_RUNNING)
+    g_cond_wait (&roc_finish_cond, &roc_finish_mutex);
+  g_mutex_unlock (&roc_finish_mutex);
+
+  g_assert (thread_state == THREAD_CANCELLED);
+
+  g_object_unref (cancellable);
+}
+
+/* test_return_on_cancel_atomic: turning return-on-cancel on/off is
+ * non-racy
+ */
+
+GMutex roca_mutex_1, roca_mutex_2;
+GCond roca_cond_1, roca_cond_2;
+
+static void
+return_on_cancel_atomic_callback (GObject      *object,
+                                  GAsyncResult *result,
+                                  gpointer      user_data)
+{
+  gboolean *callback_ran = user_data;
+  GError *error = NULL;
+  gssize ret;
+
+  g_assert (g_thread_self () == main_thread);
+
+  g_assert (object == NULL);
+  g_assert (g_task_is_valid (result, object));
+  g_assert (g_async_result_get_user_data (result) == user_data);
+  g_assert (g_task_had_error (G_TASK (result)));
+
+  ret = g_task_propagate_int (G_TASK (result), &error);
+  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+  g_clear_error (&error);
+  g_assert_cmpint (ret, ==, -1);
+
+  *callback_ran = TRUE;
+  g_main_loop_quit (loop);
+}
+
+static gboolean
+idle_quit_loop (gpointer user_data)
+{
+  g_main_loop_quit (loop);
+  return FALSE;
+}
+
+static void
+return_on_cancel_atomic_thread (GTask        *task,
+                                gpointer      source_object,
+                                gpointer      task_data,
+                                GCancellable *cancellable)
+{
+  gint *state = task_data;
+
+  g_assert (source_object == g_task_get_source_object (task));
+  g_assert (task_data == g_task_get_task_data (task));
+  g_assert (cancellable == g_task_get_cancellable (task));
+
+  g_assert (g_thread_self () != main_thread);
+  g_assert_cmpint (*state, ==, 0);
+
+  g_mutex_lock (&roca_mutex_1);
+  *state = 1;
+  g_cond_signal (&roca_cond_1);
+  g_mutex_unlock (&roca_mutex_1);
+
+  g_mutex_lock (&roca_mutex_2);
+  if (g_task_set_return_on_cancel (task, FALSE))
+    *state = 2;
+  else
+    *state = 3;
+  g_cond_signal (&roca_cond_2);
+  g_mutex_unlock (&roca_mutex_2);
+
+  g_mutex_lock (&roca_mutex_1);
+  if (g_task_set_return_on_cancel (task, TRUE))
+    *state = 4;
+  else
+    *state = 5;
+  g_cond_signal (&roca_cond_1);
+  g_mutex_unlock (&roca_mutex_1);
+
+  g_mutex_lock (&roca_mutex_2);
+  if (g_task_set_return_on_cancel (task, TRUE))
+    *state = 6;
+  else
+    *state = 7;
+  g_cond_signal (&roca_cond_2);
+  g_mutex_unlock (&roca_mutex_2);
+
+  g_task_return_int (task, magic);
+}
+
+static void
+test_return_on_cancel_atomic (void)
+{
+  GTask *task;
+  GCancellable *cancellable;
+  volatile gint state;
+  gboolean callback_ran;
+
+  cancellable = g_cancellable_new ();
+  g_mutex_lock (&roca_mutex_1);
+  g_mutex_lock (&roca_mutex_2);
+
+  /* If we don't cancel it, each set_return_on_cancel() call will succeed */
+  state = 0;
+  callback_ran = FALSE;
+  task = g_task_new (NULL, cancellable, return_on_cancel_atomic_callback, &callback_ran);
+  g_task_set_return_on_cancel (task, TRUE);
+
+  g_task_set_task_data (task, (gpointer)&state, NULL);
+  g_task_run_in_thread (task, return_on_cancel_atomic_thread);
+  g_object_unref (task);
+
+  g_assert_cmpint (state, ==, 0);
+
+  while (state == 0)
+    g_cond_wait (&roca_cond_1, &roca_mutex_1);
+  g_assert (state == 1);
+
+  while (state == 1)
+    g_cond_wait (&roca_cond_2, &roca_mutex_2);
+  g_assert (state == 2);
+
+  while (state == 2)
+    g_cond_wait (&roca_cond_1, &roca_mutex_1);
+  g_assert (state == 4);
+
+  while (state == 4)
+    g_cond_wait (&roca_cond_2, &roca_mutex_2);
+  g_assert (state == 6);
+
+  /* callback assumes there'll be a cancelled error */
+  g_cancellable_cancel (cancellable);
+
+  g_assert (callback_ran == FALSE);
+  g_main_loop_run (loop);
+  g_assert (callback_ran == TRUE);
+
+  g_cancellable_reset (cancellable);
+
+
+  /* If we cancel while it's temporarily not return-on-cancel, the
+   * task won't complete right away, and further
+   * g_task_set_return_on_cancel() calls will return FALSE.
+   */
+  state = 0;
+  callback_ran = FALSE;
+  task = g_task_new (NULL, cancellable, return_on_cancel_atomic_callback, &callback_ran);
+  g_task_set_return_on_cancel (task, TRUE);
+
+  g_task_set_task_data (task, (gpointer)&state, NULL);
+  g_task_run_in_thread (task, return_on_cancel_atomic_thread);
+  g_object_unref (task);
+
+  g_assert_cmpint (state, ==, 0);
+
+  while (state == 0)
+    g_cond_wait (&roca_cond_1, &roca_mutex_1);
+  g_assert (state == 1);
+  g_assert (g_task_get_return_on_cancel (task));
+
+  while (state == 1)
+    g_cond_wait (&roca_cond_2, &roca_mutex_2);
+  g_assert (state == 2);
+  g_assert (!g_task_get_return_on_cancel (task));
+
+  g_cancellable_cancel (cancellable);
+  g_idle_add (idle_quit_loop, NULL);
+  g_main_loop_run (loop);
+  g_assert (callback_ran == FALSE);
+
+  while (state == 2)
+    g_cond_wait (&roca_cond_1, &roca_mutex_1);
+  g_assert (state == 5);
+  g_assert (!g_task_get_return_on_cancel (task));
+
+  g_main_loop_run (loop);
+  g_assert (callback_ran == TRUE);
+
+  while (state == 5)
+    g_cond_wait (&roca_cond_2, &roca_mutex_2);
+  g_assert (state == 7);
+
+  g_object_unref (cancellable);
+  g_mutex_unlock (&roca_mutex_1);
+  g_mutex_unlock (&roca_mutex_2);
+}
+
+/* test_return_pointer: memory management of pointer returns */
+
+static void
+test_return_pointer (void)
+{
+  GObject *object, *ret;
+  GTask *task;
+  GCancellable *cancellable;
+  GError *error = NULL;
+
+  /* If we don't read back the return value, the task will
+   * run its destroy notify.
+   */
+  object = (GObject *)g_dummy_object_new ();
+  g_assert_cmpint (object->ref_count, ==, 1);
+  g_object_add_weak_pointer (object, (gpointer *)&object);
+
+  task = g_task_new (NULL, NULL, NULL, NULL);
+  g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
+  g_task_return_pointer (task, object, g_object_unref);
+  g_assert_cmpint (object->ref_count, ==, 1);
+
+  g_object_unref (task);
+  g_assert (task == NULL);
+  g_assert (object == NULL);
+
+  /* Likewise, if the return value is overwritten by an error */
+  object = (GObject *)g_dummy_object_new ();
+  g_assert_cmpint (object->ref_count, ==, 1);
+  g_object_add_weak_pointer (object, (gpointer *)&object);
+
+  cancellable = g_cancellable_new ();
+  task = g_task_new (NULL, cancellable, NULL, NULL);
+  g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
+  g_task_return_pointer (task, object, g_object_unref);
+  g_assert_cmpint (object->ref_count, ==, 1);
+  g_cancellable_cancel (cancellable);
+  g_assert_cmpint (object->ref_count, ==, 1);
+
+  ret = g_task_propagate_pointer (task, &error);
+  g_assert (ret == NULL);
+  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+  g_clear_error (&error);
+  g_assert_cmpint (object->ref_count, ==, 1);
+
+  g_object_unref (task);
+  g_object_unref (cancellable);
+  g_assert (task == NULL);
+  g_assert (object == NULL);
+  
+  /* If we read back the return value, we steal its ref */
+  object = (GObject *)g_dummy_object_new ();
+  g_assert_cmpint (object->ref_count, ==, 1);
+  g_object_add_weak_pointer (object, (gpointer *)&object);
+
+  task = g_task_new (NULL, NULL, NULL, NULL);
+  g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
+  g_task_return_pointer (task, object, g_object_unref);
+  g_assert_cmpint (object->ref_count, ==, 1);
+
+  ret = g_task_propagate_pointer (task, &error);
+  g_assert_no_error (error);
+  g_assert (ret == object);
+  g_assert_cmpint (object->ref_count, ==, 1);
+
+  g_object_unref (task);
+  g_assert (task == NULL);
+  g_assert_cmpint (object->ref_count, ==, 1);
+  g_object_unref (object);
+  g_assert (object == NULL);
+}
+
+/* test_object_keepalive: GTask takes a ref on its source object */
+
+static GObject *keepalive_object;
+
+static void
+keepalive_callback (GObject      *object,
+                    GAsyncResult *result,
+                    gpointer      user_data)
+{
+  gssize *result_out = user_data;
+  GError *error = NULL;
+
+  g_assert (object == keepalive_object);
+  g_assert (g_task_is_valid (result, object));
+  g_assert (g_async_result_get_user_data (result) == user_data);
+  g_assert (!g_task_had_error (G_TASK (result)));
+
+  *result_out = g_task_propagate_int (G_TASK (result), &error);
+  g_assert_no_error (error);
+
+  g_main_loop_quit (loop);
+}
+
+static void
+test_object_keepalive (void)
+{
+  GObject *object;
+  GTask *task;
+  gssize result;
+  int ref_count;
+
+  keepalive_object = object = (GObject *)g_dummy_object_new ();
+  g_object_add_weak_pointer (object, (gpointer *)&object);
+
+  task = g_task_new (object, NULL, keepalive_callback, &result);
+  g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
+
+  ref_count = object->ref_count;
+  g_assert_cmpint (ref_count, >, 1);
+
+  g_assert (g_task_get_source_object (task) == object);
+  g_assert (g_async_result_get_source_object (G_ASYNC_RESULT (task)) == object);
+  g_assert_cmpint (object->ref_count, ==, ref_count + 1);
+  g_object_unref (object);
+
+  g_object_unref (object);
+  g_assert (object != NULL);
+
+  g_task_return_int (task, magic);
+  g_main_loop_run (loop);
+
+  g_assert (object != NULL);
+  g_assert_cmpint (result, ==, magic);
+
+  g_object_unref (task);
+  g_assert (task == NULL);
+  g_assert (object == NULL);
+}
+
+/* test_legacy_error: legacy GSimpleAsyncResult handling */
+static void test_legacy_error (void);
+
+static void
+legacy_error_callback (GObject      *object,
+                       GAsyncResult *result,
+                       gpointer      user_data)
+{
+  gssize *result_out = user_data;
+  GError *error = NULL;
+
+  g_assert (object == NULL);
+  g_assert (g_async_result_is_tagged (result, test_legacy_error));
+  g_assert (g_async_result_get_user_data (result) == user_data);
+
+  if (g_async_result_legacy_propagate_error (result, &error))
+    {
+      g_assert (!g_task_is_valid (result, object));
+      G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
+      g_assert (g_simple_async_result_is_valid (result, object, test_legacy_error));
+      G_GNUC_END_IGNORE_DEPRECATIONS;
+
+      g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED);
+      *result_out = -2;
+    }
+  else
+    {
+      g_assert (g_task_is_valid (result, object));
+
+      *result_out = g_task_propagate_int (G_TASK (result), NULL);
+      /* Might be error, might not */
+    }
+
+  g_main_loop_quit (loop);
+}
+
+static gboolean
+legacy_error_return (gpointer user_data)
+{
+  if (G_IS_TASK (user_data))
+    {
+      GTask *task = user_data;
+
+      g_task_return_int (task, magic);
+      g_object_unref (task);
+    }
+  else
+    {
+      GSimpleAsyncResult *simple = user_data;
+
+      G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
+      g_simple_async_result_set_error (simple,
+                                       G_IO_ERROR,
+                                       G_IO_ERROR_FAILED,
+                                       "Failed");
+      g_simple_async_result_complete (simple);
+      G_GNUC_END_IGNORE_DEPRECATIONS;
+      g_object_unref (simple);
+    }
+
+  return FALSE;
+}
+
+static void
+test_legacy_error (void)
+{
+  GTask *task;
+  GSimpleAsyncResult *simple;
+  gssize result;
+
+  /* GTask success */
+  task = g_task_new (NULL, NULL, legacy_error_callback, &result);
+  g_task_set_source_tag (task, test_legacy_error);
+  g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
+
+  g_idle_add (legacy_error_return, task);
+  g_main_loop_run (loop);
+
+  g_assert_cmpint (result, ==, magic);
+  g_assert (task == NULL);
+
+  /* GTask error */
+  task = g_task_new (NULL, NULL, legacy_error_callback, &result);
+  g_task_set_source_tag (task, test_legacy_error);
+  g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
+
+  g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
+                           "Failed");
+  g_object_unref (task);
+  g_main_loop_run (loop);
+
+  g_assert_cmpint (result, ==, -1);
+  g_assert (task == NULL);
+
+  /* GSimpleAsyncResult error */
+  G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
+  simple = g_simple_async_result_new (NULL, legacy_error_callback, &result,
+                                      test_legacy_error);
+  G_GNUC_END_IGNORE_DEPRECATIONS;
+  g_object_add_weak_pointer (G_OBJECT (simple), (gpointer *)&simple);
+
+  g_idle_add (legacy_error_return, simple);
+  g_main_loop_run (loop);
+
+  g_assert_cmpint (result, ==, -2);
+  g_assert (simple == NULL);
+}
+
+
+int
+main (int argc, char **argv)
+{
+  int ret;
+
+  g_type_init ();
+  g_test_init (&argc, &argv, NULL);
+
+  loop = g_main_loop_new (NULL, FALSE);
+  main_thread = g_thread_self ();
+  magic = g_get_monotonic_time ();
+
+  g_test_add_func ("/gtask/basic", test_basic);
+  g_test_add_func ("/gtask/error", test_error);
+  g_test_add_func ("/gtask/return-from-same-iteration", test_return_from_same_iteration);
+  g_test_add_func ("/gtask/return-from-toplevel", test_return_from_toplevel);
+  g_test_add_func ("/gtask/return-from-anon-thread", test_return_from_anon_thread);
+  g_test_add_func ("/gtask/return-from-wrong-thread", test_return_from_wrong_thread);
+  g_test_add_func ("/gtask/no-callback", test_no_callback);
+  g_test_add_func ("/gtask/report-error", test_report_error);
+  g_test_add_func ("/gtask/priority", test_priority);
+  g_test_add_func ("/gtask/check-cancellable", test_check_cancellable);
+  g_test_add_func ("/gtask/return-if-cancelled", test_return_if_cancelled);
+  g_test_add_func ("/gtask/run-in-thread", test_run_in_thread);
+  g_test_add_func ("/gtask/run-in-thread-sync", test_run_in_thread_sync);
+  g_test_add_func ("/gtask/run-in-thread-priority", test_run_in_thread_priority);
+  g_test_add_func ("/gtask/return-on-cancel", test_return_on_cancel);
+  g_test_add_func ("/gtask/return-on-cancel-sync", test_return_on_cancel_sync);
+  g_test_add_func ("/gtask/return-on-cancel-atomic", test_return_on_cancel_atomic);
+  g_test_add_func ("/gtask/return-pointer", test_return_pointer);
+  g_test_add_func ("/gtask/object-keepalive", test_object_keepalive);
+  g_test_add_func ("/gtask/legacy-error", test_legacy_error);
+
+  ret = g_test_run();
+
+  g_main_loop_unref (loop);
+
+  return ret;
+}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]