[gnome-devel-docs] programming-guidelines: Add a page on asynchronous programming



commit 1dca93536254547bfebfdba0bed2b742ff32f38f
Author: Philip Withnall <philip withnall collabora co uk>
Date:   Tue Feb 24 15:19:26 2015 +0000

    programming-guidelines: Add a page on asynchronous programming
    
    This covers various techniques for using and defining GAsyncResult-style
    asynchronous methods, based around the use of examples.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=745092

 programming-guidelines/C/async-programming.page | 1200 +++++++++++++++++++++++
 programming-guidelines/C/main-contexts.page     |    8 +-
 programming-guidelines/C/threading.page         |    2 +-
 programming-guidelines/Makefile.am              |    1 +
 4 files changed, 1208 insertions(+), 3 deletions(-)
---
diff --git a/programming-guidelines/C/async-programming.page b/programming-guidelines/C/async-programming.page
new file mode 100644
index 00000000..c61b91b1
--- /dev/null
+++ b/programming-guidelines/C/async-programming.page
@@ -0,0 +1,1200 @@
+<page xmlns="http://projectmallard.org/1.0/";
+      xmlns:its="http://www.w3.org/2005/11/its";
+      xmlns:xi="http://www.w3.org/2003/XInclude";
+      type="topic"
+      id="async-programming">
+
+  <info>
+    <link type="guide" xref="index#specific-how-tos"/>
+
+    <credit type="author copyright">
+      <name>Philip Withnall</name>
+      <email its:translate="no">philip withnall collabora co uk</email>
+      <years>2015</years>
+    </credit>
+
+    <include href="cc-by-sa-3-0.xml" xmlns="http://www.w3.org/2001/XInclude"/>
+
+    <desc>
+      Use of GLib-style asynchronous methods in various situations
+    </desc>
+  </info>
+
+  <title>Asynchronous Programming</title>
+
+  <synopsis>
+    <title>Summary</title>
+
+    <list>
+      <item><p>
+        Use asynchronous calls in preference to synchronous calls or explicit
+        use of threads (<link xref="#concepts"/>)
+      </p></item>
+      <item><p>
+        Learn and follow the GLib pattern for declaring asynchronous APIs
+        (<link xref="#api-pattern"/>)
+      </p></item>
+      <item><p>
+        Place callbacks from asynchronous functions in order down the file, so
+        control flow is easy to follow (<link xref="#single-call"/>)
+      </p></item>
+      <item><p>
+        Use the presence of a
+        <link href="https://developer.gnome.org/gio/stable/GTask.html";><code>GTask</code></link>
+        or <link 
href="https://developer.gnome.org/gio/stable/GCancellable.html";><code>GCancellable</code></link>
+        to indicate whether an operation is ongoing
+        (<link xref="#single-call"/>, <link xref="#gtask"/>)
+      </p></item>
+      <item><p>
+        If running operations in parallel, track how many operations are yet to
+        start, and how many are yet to finish — the overall operation is
+        complete once both counts are zero
+        (<link xref="#parallel"/>)
+      </p></item>
+      <item><p>
+        Separate state for operations into ‘task data’ structures for
+        <link href="https://developer.gnome.org/gio/stable/GTask.html";><code>GTask</code>s</link>,
+        allowing operations to be reused more easily without needing changes to
+        global state handling (<link xref="#gtask"/>)
+      </p></item>
+      <item><p>
+        Consider how asynchronous methods on an object instance interact with
+        finalization of that instance (<link xref="#lifetimes"/>)
+      </p></item>
+    </list>
+  </synopsis>
+
+  <section id="concepts">
+    <title>Concepts</title>
+
+    <p>
+      GLib supports <em>asynchronous</em> programming, where long-running
+      operations can be started, run ‘in the background’, and a callback invoked
+      when they are finished and their results are available. This is in direct
+      contrast to <em>synchronous</em> long-running operations, which are a
+      single function call which blocks program control flow until complete.
+    </p>
+
+    <p>
+      As discussed in <link xref="main-contexts"/> and
+      <link xref="threading#when-to-use-threading"/>, asynchronous operations
+      should be favoured over synchronous ones and over explicit use of
+      threading. They do not block the main context like sychronous operations
+      do; and are easier to use correctly than threads. They often also have a
+      lower performance penalty than spawning a thread and sending work to it.
+    </p>
+  </section>
+
+  <section id="api-pattern">
+    <title>API Pattern</title>
+
+    <p>
+      Asynchronous calls follow a standard pattern in GLib code. For an
+      operation named <code>load_data</code> on the <code>File</code> class in
+      the <code>Foo</code> namespace, there will be:
+    </p>
+    <list>
+      <item>
+        <code mime="text/x-csrc">
+foo_file_load_data_async (FooFile             *self,
+                          …,
+                          GCancellable        *cancellable,
+                          GAsyncReadyCallback  callback,
+                          gpointer             user_data)</code>
+      </item>
+      <item>
+        <code mime="text/x-csrc">
+foo_file_load_data_finish (FooFile       *self,
+                           GAsyncResult  *result,
+                           …,
+                           GError       **error)</code>
+      </item>
+    </list>
+
+    <p>
+      The <code>…</code> parameters to <code>foo_file_load_data_async()</code>
+      are those specific to the operation — in this case, perhaps the size of a
+      buffer to load into. Similarly for
+      <code>foo_file_load_data_finish()</code> they are the operation-specific
+      return values — perhaps a location to return a content type string in this
+      case.
+    </p>
+
+    <p>
+      When <code>foo_file_load_data_async()</code> is called, it schedules the
+      load operation in the background (as a new file descriptor on the
+      <link xref="main-contexts"><code>GMainContext</code></link> or as a worker
+      thread, for example), then returns without blocking.
+    </p>
+
+    <p>
+      When the operation is complete, the <code>callback</code> is executed in
+      the same <code>GMainContext</code> as the original asynchronous call. The
+      callback is invoked <em>exactly</em> once, whether the operation succeeded
+      or failed.
+    </p>
+
+    <p>
+      From the callback, <code>foo_file_load_data_finish()</code> may be called
+      by the user’s code to retrieve return values and error details, passing
+      the <link 
href="https://developer.gnome.org/gio/stable/GAsyncResult.html";><code>GAsyncResult</code></link>
+      instance which was passed to the callback.
+    </p>
+  </section>
+
+  <section id="lifetimes">
+    <title>Operation Lifetimes</title>
+
+    <p>
+      When writing asynchronous operations, it is common to write them as
+      methods of a class. In this case, it is important to define how ongoing
+      operations on a class instance interact with finalization of that
+      instance. There are two approaches:
+    </p>
+
+    <terms>
+      <item>
+        <title>Strong</title>
+        <p>
+          The ongoing operation keeps a reference to the class instance, forcing
+          it to remain alive for the duration of the operation. The class should
+          provide some kind of ‘close’ or ‘cancel’ method which can be used by
+          other classes to force cancellation of the operation and allow that
+          instance to be finalized.
+        </p>
+      </item>
+
+      <item>
+        <title>Weak</title>
+        <p>
+          The ongoing operation does <em>not</em> keep a reference to the class
+          instance, and the class cancels the operation (using
+          <link 
href="https://developer.gnome.org/gio/stable/GCancellable.html#g-cancellable-cancel";><code>g_cancellable_cancel()</code></link>)
+          in its dispose function.
+        </p>
+      </item>
+    </terms>
+
+    <p>
+      Which approach is used depends on the class’ design. A class which wraps
+      a particular operation (perhaps a <code>MyFileTransfer</code> class, for
+      example) might want to use the <em style="strong">weak</em> approach.
+      A class which manages multiple network connections and asynchronous
+      operations on them may use the <em style="strong">strong</em> approach
+      instead. Due to incoming network connections, for example, it might not be
+      in complete control of the scheduling of its asynchronous calls, so the
+      weak approach would not be appropriate — any code dropping a reference to
+      the object could not be sure it was not accidentally killing a new network
+      connection.
+    </p>
+  </section>
+
+  <section id="async-examples">
+    <title>Examples of Using Asynchronous Functions</title>
+
+    <p>
+      It is often the case that multiple asynchronous calls need to be used to
+      complete an operation. For example, opening a file for reading, then
+      performing a couple of reads, and then closing the file. Or opening
+      several network sockets in parallel and waiting until they are all open
+      before continuing with other work. Some examples of these situations are
+      given below.
+    </p>
+
+    <section id="single-call">
+      <title>Single Operation</title>
+
+      <p>
+        A single asynchronous call requires two functions: one to start the
+        operation, and one to complete it. In C, the demanding part of
+        performing an asynchronous call is correctly storing state between these
+        two functions, and handling changes to that state in the time between
+        those two functions being called. For example, cancellation of an
+        ongoing asynchronous call is a state change, and if not implemented
+        carefully, any UI updates (for example) made when cancelling an
+        operation will be undone by updates in the operation’s callback.
+      </p>
+
+      <example>
+        <p>
+          This example demonstrates copying a file from one location in the file
+          system to another. The key principles demonstrated here are:
+        </p>
+        <list>
+          <item><p>
+            Placing the <code>copy_button_clicked_cb()</code> (start) and
+            <code>copy_finish_cb()</code> (finish) functions in order by using
+            a forward declaration for <code>copy_finish_cb()</code>. This means
+            the control flow continues linearly down the file, rather than
+            getting to the bottom of <code>copy_button_clicked_cb()</code> and
+            resuming in <code>copy_finish_cb()</code> somewhere else in the
+            file.
+          </p></item>
+          <item><p>
+            Use of a
+            <link 
href="https://developer.gnome.org/gio/stable/GCancellable.html";><code>GCancellable</code></link>
+            to allow cancelling the operation
+            after it has started. The code in
+            <code>cancel_button_clicked_cb()</code> is very simple: as the
+            <code>copy_finish_cb()</code> callback is <em>guaranteed</em> to be
+            invoked when the operation completes (even when completing early
+            due to cancellation), all the UI and state updates for cancellation
+            can be handled there, rather than in
+            <code>cancel_button_clicked_cb()</code>.
+          </p></item>
+          <item><p>
+            An operation is ongoing exactly while
+            <code>MyObjectPrivate.copy_cancellable</code> is
+            non-<code>NULL</code>, making it easy to track running operations.
+            Note that this means only one file copy operation can be started
+            via <code>copy_button_clicked_cb()</code> at a time. One
+            <code>GCancellable</code> cannot easily be used for multiple
+            operations like this.
+          </p></item>
+        </list>
+
+        <code mime="text/x-csrc" style="valid">
+static void
+copy_finish_cb (GObject      *source_object,
+                GAsyncResult *result,
+                gpointer      user_data);
+
+static void
+copy_button_clicked_cb (GtkButton *button
+                        gpointer   user_data)
+{
+  MyObjectPrivate *priv;
+  GFile *source = NULL, *destination = NULL;  /* owned */
+
+  priv = my_object_get_instance_private (MY_OBJECT (user_data));
+
+  /* Operation already in progress? */
+  if (priv->copy_cancellable != NULL)
+    {
+      g_debug ("Copy already in progress.");
+      return;
+    }
+
+  /* Build source and destination file paths. */
+  source = g_file_new_for_path (/* some path generated from UI */);
+  destination = g_file_new_for_path (/* some other path generated from UI */);
+
+  /* Set up a cancellable. */
+  priv->copy_cancellable = g_cancellable_new ();
+
+  g_file_copy_async (source, destination, G_FILE_COPY_NONE, G_PRIORITY_DEFAULT,
+                     priv->copy_cancellable, NULL, NULL,
+                     copy_finish_cb, user_data);
+
+  g_object_unref (destination);
+  g_object_unref (source);
+
+  /* Update UI to show copy is in progress. */
+  …
+}
+
+static void
+copy_finish_cb (GObject      *source_object,
+                GAsyncResult *result,
+                gpointer      user_data)
+{
+  MyObjectPrivate *priv;
+  GFile *source;  /* unowned */
+  GError *error = NULL;
+
+  source = G_FILE (source_object);
+  priv = my_object_get_instance_private (MY_OBJECT (user_data));
+
+  /* Handle completion of the operation. */
+  g_file_copy_finish (source, result, &amp;error);
+
+  if (error != NULL &amp;&amp;
+      !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    {
+      /* Should update the UI to signal failure.
+       * Ignore failure due to cancellation. */
+      g_warning ("Failed to copy file: %s", error->message);
+    }
+
+  g_clear_error (&amp;error);
+
+  /* Clear the cancellable to signify the operation has finished. */
+  g_clear_object (&amp;priv->copy_cancellable);
+
+  /* Update UI to show copy as complete. */
+  …
+}
+
+static void
+cancel_button_clicked_cb (GtkButton *button,
+                          gpointer   user_data)
+{
+  MyObjectPrivate *priv;
+  GFile *source = NULL, *destination = NULL;  /* owned */
+
+  priv = my_object_get_instance_private (MY_OBJECT (user_data));
+
+  /* Operation in progress? No-op if @copy_cancellable is %NULL. */
+  g_cancellable_cancel (priv->copy_cancellable);
+}
+
+static void
+my_object_dispose (GObject *obj)
+{
+  MyObjectPrivate *priv;
+
+  priv = my_object_get_instance_private (MY_OBJECT (obj));
+
+  /* Cancel any ongoing copy operation.
+   *
+   * This ensures that if #MyObject is disposed part-way through a copy, the
+   * callback doesn’t get invoked with an invalid #MyObject pointer. */
+  g_cancellable_cancel (priv->copy_cancellable);
+
+  /* Do other dispose calls here. */
+  …
+
+  /* Chain up. */
+  G_OBJECT_CLASS (my_object_parent_class)->dispose (obj);
+}</code>
+
+        <p>
+          For comparison, here is the same code implemented using the
+          <em>synchronous</em> version of
+          <link 
href="https://developer.gnome.org/gio/stable/GFile.html#g-file-copy";><code>g_file_copy()</code></link>.
+          Note how the order of statements is almost identical. Cancellation
+          cannot be supported here, as the UI is blocked from receiving ‘click’
+          events on the cancellation button while the copy is ongoing, so
+          <code>NULL</code> is passed to the <code>GCancellable</code>
+          parameter. This is the main reason why this code should <em>not</em>
+          be used in practice.
+        </p>
+
+        <code mime="text/x-csrc" style="invalid">
+static void
+copy_button_clicked_cb (GtkButton *button
+                        gpointer   user_data)
+{
+  MyObjectPrivate *priv;
+  GFile *source = NULL, *destination = NULL;  /* owned */
+
+  priv = my_object_get_instance_private (MY_OBJECT (user_data));
+
+  /* Build source and destination file paths. */
+  source = g_file_new_for_path (/* some path generated from UI */);
+  destination = g_file_new_for_path (/* some other path generated from UI */);
+
+  g_file_copy (source, destination, G_FILE_COPY_NONE,
+               NULL  /* cancellable */, NULL, NULL,
+               &amp;error);
+
+  g_object_unref (destination);
+  g_object_unref (source);
+
+  /* Handle completion of the operation. */
+  if (error != NULL)
+    {
+      /* Should update the UI to signal failure.
+       * Ignore failure due to cancellation. */
+      g_warning ("Failed to copy file: %s", error->message);
+    }
+
+  g_clear_error (&amp;error);
+
+  /* Update UI to show copy as complete. */
+  …
+}</code>
+      </example>
+    </section>
+
+    <section id="series">
+      <title>Operations in Series</title>
+
+      <p>
+        A common situation is to run multiple asynchronous operations in series,
+        when each operation depends on the previous one completing.
+      </p>
+
+      <example>
+        <p>
+          In this example, the application reads a socket address from a file,
+          opens a connection to that address, reads a message, and then
+          finishes.
+        </p>
+
+        <p>
+          Key points in this example are:
+        </p>
+        <list>
+          <item><p>
+            Each callback is numbered consistently, and they are all placed in
+            order in the file so the code follows sequentially.
+          </p></item>
+          <item><p>
+            As in <link xref="#single-call"/>, a single
+            <code>GCancellable</code> indicates that the series of operations is
+            ongoing. Cancelling it aborts the entire sequence.
+          </p></item>
+          <item><p>
+            As in <link xref="#single-call"/>, the pending operation is
+            cancelled if the owning <code>MyObject</code> instance is disposed,
+            to prevent callbacks being called later with an invalid
+            <code>MyObject</code> pointer.
+          </p></item>
+        </list>
+
+        <p>
+          <link xref="#gtask"/> gives a version of this example wrapped in a
+          <link href="https://developer.gnome.org/gio/stable/GTask.html";><code>GTask</code></link>
+          for convenience.
+        </p>
+
+        <code mime="text/x-csrc" style="valid">
+static void
+connect_to_server_cb1 (GObject      *source_object,
+                       GAsyncResult *result,
+                       gpointer      user_data);
+static void
+connect_to_server_cb2 (GObject      *source_object,
+                       GAsyncResult *result,
+                       gpointer      user_data);
+static void
+connect_to_server_cb3 (GObject      *source_object,
+                       GAsyncResult *result,
+                       gpointer      user_data);
+
+static void
+connect_to_server (MyObject *self)
+{
+  MyObjectPrivate *priv;
+  GFile *address_file = NULL;  /* owned */
+
+  priv = my_object_get_instance_private (self);
+
+  if (priv->connect_cancellable != NULL)
+    {
+      /* Already connecting. */
+      return;
+    }
+
+  /* Set up a cancellable. */
+  priv->connect_cancellable = g_cancellable_new ();
+
+  /* Read the socket address. */
+  address_file = build_address_file ();
+  g_file_load_contents_async (address_file, priv->connect_cancellable,
+                              connect_to_server_cb1, self);
+  g_object_unref (address_file);
+}
+
+static void
+connect_to_server_cb1 (GObject      *source_object,
+                       GAsyncResult *result,
+                       gpointer      user_data)
+{
+  MyObject *self;
+  MyObjectPrivate *priv;
+  GFile *address_file;  /* unowned */
+  gchar *address = NULL;  /* owned */
+  gsize address_size = 0;
+  GInetAddress *inet_address = NULL;  /* owned */
+  GInetSocketAddress *inet_socket_address = NULL;  /* owned */
+  guint16 port = 123;
+  GSocketClient *socket_client = NULL;  /* owned */
+  GError *error = NULL;
+
+  address_file = G_FILE (source_object);
+  self = MY_OBJECT (user_data);
+  priv = my_object_get_instance_private (self);
+
+  /* Finish loading the address. */
+  g_file_load_contents_finish (address_file, result, &amp;address,
+                               &amp;address_size, NULL, &amp;error);
+
+  if (error != NULL)
+    {
+      goto done;
+    }
+
+  /* Parse the address. */
+  inet_address = g_inet_address_new_from_string (address);
+
+  if (inet_address == NULL)
+    {
+      /* Error. */
+      g_set_error (&amp;error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+                   "Invalid address ‘%s’.", address);
+      goto done;
+    }
+
+  inet_socket_address = g_inet_socket_address_new (inet_address, port);
+
+  /* Connect to the given address. */
+  socket_client = g_socket_client_new ();
+
+  g_socket_client_connect_async (socket_client,
+                                 G_SOCKET_CONNECTABLE (inet_socket_address),
+                                 priv->connect_cancellable,
+                                 connect_to_server_cb2,
+                                 self);
+
+done:
+  if (error != NULL)
+    {
+      /* Stop the operation. */
+      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        {
+          g_warning ("Failed to load server address: %s", error->message);
+        }
+
+      g_clear_object (&amp;priv->connect_cancellable);
+      g_error_free (error);
+    }
+
+  g_free (address);
+  g_clear_object (&amp;inet_address);
+  g_clear_object (&amp;inet_socket_address);
+  g_clear_object (&amp;socket_client);
+}
+
+static void
+connect_to_server_cb2 (GObject      *source_object,
+                       GAsyncResult *result,
+                       gpointer      user_data)
+{
+  MyObject *self;
+  MyObjectPrivate *priv;
+  GSocketClient *socket_client;  /* unowned */
+  GSocketConnection *connection = NULL;  /* owned */
+  GInputStream *input_stream;  /* unowned */
+  GError *error = NULL;
+
+  socket_client = G_SOCKET_CLIENT (source_object);
+  self = MY_OBJECT (user_data);
+  priv = my_object_get_instance_private (self);
+
+  /* Finish connecting to the socket. */
+  connection = g_socket_client_connect_finish (socket_client, result,
+                                               &amp;error);
+
+  if (error != NULL)
+    {
+      goto done;
+    }
+
+  /* Store a reference to the connection so it is kept open while we read from
+   * it: #GInputStream does not keep a reference to a #GIOStream which contains
+   * it. */
+  priv->connection = g_object_ref (connection);
+
+  /* Read a message from the connection. This uses a single buffer stored in
+   * #MyObject, meaning that only one connect_to_server() operation can run at
+   * any time. The buffer could instead be allocated dynamically if this is a
+   * problem. */
+  input_stream = g_io_stream_get_input_stream (G_IO_STREAM (connection));
+
+  g_input_stream_read_async (input_stream,
+                             priv->message_buffer,
+                             sizeof (priv->message_buffer),
+                             G_PRIORITY_DEFAULT, priv->connect_cancellable,
+                             connect_to_server_cb3, self);
+
+done:
+  if (error != NULL)
+    {
+      /* Stop the operation. */
+      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        {
+          g_warning ("Failed to connect to server: %s", error->message);
+        }
+
+      g_clear_object (&amp;priv->connect_cancellable);
+      g_clear_object (&amp;priv->connection);
+      g_error_free (error);
+    }
+
+  g_clear_object (&amp;connection);
+}
+
+static void
+connect_to_server_cb3 (GObject      *source_object,
+                       GAsyncResult *result,
+                       gpointer      user_data)
+{
+  MyObject *self;
+  MyObjectPrivate *priv;
+  GInputStream *input_stream;  /* unowned */
+  gssize len = 0;
+  GError *error = NULL;
+
+  input_stream = G_INPUT_STREAM (source_object);
+  self = MY_OBJECT (user_data);
+  priv = my_object_get_instance_private (self);
+
+  /* Finish reading from the socket. */
+  len = g_input_stream_read_finish (input_stream, result, &amp;error);
+
+  if (error != NULL)
+    {
+      goto done;
+    }
+
+  /* Handle the message. */
+  g_assert_cmpint (len, >=, 0);
+  g_assert_cmpuint ((gsize) len, &lt;=, sizeof (priv->message_buffer));
+
+  handle_received_message (self, priv->message_buffer, len, &amp;error);
+
+  if (error != NULL)
+    {
+      goto done;
+    }
+
+done:
+  /* Unconditionally mark the operation as finished.
+   *
+   * The streams should automatically close as this
+   * last reference is dropped. */
+  g_clear_object (&amp;priv->connect_cancellable);
+  g_clear_object (&amp;priv->connection);
+
+  if (error != NULL)
+    {
+      /* Warn about the error. */
+      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        {
+          g_warning ("Failed to read from the server: %s", error->message);
+        }
+
+      g_error_free (error);
+    }
+}
+
+static void
+my_object_dispose (GObject *obj)
+{
+  MyObjectPrivate *priv;
+
+  priv = my_object_get_instance_private (MY_OBJECT (obj));
+
+  /* Cancel any ongoing connection operations.
+   *
+   * This ensures that if #MyObject is disposed part-way through the
+   * connect_to_server() sequence of operations, the sequence gets cancelled and
+   * doesn’t continue with an invalid #MyObject pointer. */
+  g_cancellable_cancel (priv->connect_cancellable);
+
+  /* Do other dispose calls here. */
+  …
+
+  /* Chain up. */
+  G_OBJECT_CLASS (my_object_parent_class)->dispose (obj);
+}</code>
+      </example>
+    </section>
+
+    <section id="parallel">
+      <title>Operations in Parallel</title>
+
+      <p>
+        Another common situation is to run multiple asynchronous operations in
+        parallel, considering the overall operation complete when all its
+        constituents are complete.
+      </p>
+
+      <example>
+        <p>
+          In this example, the application deletes multiple files in parallel.
+        </p>
+
+        <p>
+          Key points in this example are:
+        </p>
+        <list>
+          <item><p>
+            The number of pending asynchronous operations (ones which have
+            started but not yet finished) is tracked as
+            <code>n_deletions_pending</code>. The <code>delete_files_cb()</code>
+            callback only considers the entire operation complete once this
+            reaches zero.
+          </p></item>
+          <item><p>
+            <code>n_deletions_to_start</code> tracks deletion operations being
+            started, in case
+            <link 
href="https://developer.gnome.org/gio/stable/GFile.html#g-file-delete-async";><code>g_file_delete_async()</code></link>
+            manages to use a fast path and complete synchronously (without
+            blocking).
+          </p></item>
+          <item><p>
+            As in <link xref="#single-call"/>, all pending deletions are
+            cancelled if the owning <code>MyObject</code> instance is disposed,
+            to prevent callbacks being called later with an invalid
+            <code>MyObject</code> pointer.
+          </p></item>
+        </list>
+
+        <code mime="text/x-csrc" style="valid">
+static void
+delete_files_cb (GObject      *source_object,
+                 GAsyncResult *result,
+                 gpointer      user_data);
+
+static void
+delete_files (MyObject *self,
+              GPtrArray/*&lt;owned GFile*&gt;>*/ *files)
+{
+  MyObjectPrivate *priv;
+  GFile *address_file = NULL;  /* owned */
+
+  priv = my_object_get_instance_private (self);
+
+  /* Set up a cancellable if no operation is ongoing already. */
+  if (priv->delete_cancellable == NULL)
+    {
+      priv->delete_cancellable = g_cancellable_new ();
+      priv->n_deletions_pending = 0;
+      priv->n_deletions_total = 0;
+    }
+
+  /* Update internal state, and temporarily set @n_deletions_to_start. This is
+   * used in delete_files_cb() to avoid indicating the overall operation has
+   * completed while deletions are still being started. This can happen if
+   * g_file_delete_async() completes synchronously, for example if there’s a
+   * non-blocking fast path for the given file system. */
+  priv->n_deletions_pending += files->len;
+  priv->n_deletions_total += files->len;
+  priv->n_deletions_to_start = files->len;
+
+  /* Update the UI to indicate the files are being deleted. */
+  update_ui_to_show_progress (self,
+                              priv->n_deletions_pending,
+                              priv->n_deletions_total);
+
+  /* Start all the deletion operations in parallel. They share the same
+   * #GCancellable. */
+  for (i = 0; i &lt; files->len; i++)
+    {
+      GFile *file = files->pdata[i];
+
+      priv->n_deletions_to_start--;
+      g_file_delete_async (file, G_PRIORITY_DEFAULT, priv->delete_cancellable,
+                           delete_files_cb, self);
+    }
+}
+
+static void
+delete_files_cb (GObject      *source_object,
+                 GAsyncResult *result,
+                 gpointer      user_data)
+{
+  MyObject *self;
+  MyObjectPrivate *priv;
+  GFile *file;  /* unowned */
+  GError *error = NULL;
+
+  file = G_FILE (source_object);
+  self = MY_OBJECT (user_data);
+  priv = my_object_get_instance_private (self);
+
+  /* Finish deleting the file. */
+  g_file_delete_finish (file, result, &amp;error);
+
+  if (error != NULL &amp;&amp;
+      !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    {
+      g_warning ("Error deleting file: %s", error->message);
+    }
+
+  g_clear_error (&amp;error);
+
+  /* Update the internal state. */
+  g_assert_cmpuint (priv->n_deletions_pending, >, 0);
+  priv->n_deletions_pending--;
+
+  /* Update the UI to show progress. */
+  update_ui_to_show_progress (self,
+                              priv->n_deletions_pending,
+                              priv->n_deletions_total);
+
+  /* If all deletions have completed, and no more are being started,
+   * update the UI to show completion. */
+  if (priv->n_deletions_pending == 0 &amp;&amp; priv->n_deletions_to_start == 0)
+    {
+      update_ui_to_show_completion (self);
+
+      /* Clear the operation state. */
+      g_clear_object (&amp;priv->delete_cancellable);
+      priv->n_deletions_total = 0;
+    }
+}
+
+static void
+my_object_dispose (GObject *obj)
+{
+  MyObjectPrivate *priv;
+
+  priv = my_object_get_instance_private (MY_OBJECT (obj));
+
+  /* Cancel any ongoing deletion operations.
+   *
+   * This ensures that if #MyObject is disposed part-way through the
+   * delete_files() set of operations, the set gets cancelled and
+   * doesn’t continue with an invalid #MyObject pointer. */
+  g_cancellable_cancel (priv->delete_cancellable);
+
+  /* Do other dispose calls here. */
+  …
+
+  /* Chain up. */
+  G_OBJECT_CLASS (my_object_parent_class)->dispose (obj);
+}</code>
+      </example>
+    </section>
+
+    <section id="gtask">
+      <title>Wrapping with <code>GTask</code></title>
+
+      <p>
+        Often when an asynchronous operation (or set of operations) becomes more
+        complex, it needs associated state. This is typically stored in a custom
+        structure — but defining a new structure to store the standard callback,
+        user data and cancellable tuple is laborious.
+        <link href="https://developer.gnome.org/gio/stable/GTask.html";><code>GTask</code></link>
+        eases this by providing a standardized way to wrap all three, plus extra
+        custom ‘task data’.
+      </p>
+
+      <p>
+        The use of a <code>GTask</code> can replace the use of a
+        <link 
href="https://developer.gnome.org/gio/stable/GCancellable.html";><code>GCancellable</code></link>
+        for indicating whether an operation is ongoing.
+      </p>
+
+      <example>
+        <p>
+          This example is functionally the same as <link xref="#series"/>, but
+          refactored to use a <code>GTask</code> to wrap the sequence of
+          operations.
+        </p>
+
+        <p>
+          Key points in this example are:
+        </p>
+        <list>
+          <item><p>
+            State which was in <code>MyObjectPrivate</code> in
+            <link xref="#series"/> is now in the
+            <code>ConnectToServerData</code> closure, which is set as the ‘task
+            data’ of the <code>GTask</code> representing the overall operation.
+            This means it’s automatically freed after the operation returns.
+          </p></item>
+          <item><p>
+            Furthermore, this means that manipulations of
+            <code>MyObjectPrivate</code> state are limited to the start and end
+            of the sequence of operations, so reusing the task in different
+            situations becomes easier — for example, it is now a lot easier to
+            support running multiple such tasks in parallel.
+          </p></item>
+          <item><p>
+            As the <code>GTask</code> holds a reference to
+            <code>MyObject</code>, it is impossible for the object to be
+            disposed while the sequence of operations is ongoing, so the
+            <code>my_object_dispose()</code> code has been removed. Instead, a
+            <code>my_object_close()</code> method exists to allow any pending
+            operations can be cancelled so <code>MyObject</code> can be disposed
+            when desired.
+          </p></item>
+        </list>
+
+        <code mime="text/x-csrc" style="valid">
+static void
+connect_to_server_cb1 (GObject      *source_object,
+                       GAsyncResult *result,
+                       gpointer      user_data);
+static void
+connect_to_server_cb2 (GObject      *source_object,
+                       GAsyncResult *result,
+                       gpointer      user_data);
+static void
+connect_to_server_cb3 (GObject      *source_object,
+                       GAsyncResult *result,
+                       gpointer      user_data);
+
+typedef struct {
+  GSocketConnection *connection;  /* nullable; owned */
+  guint8 message_buffer[128];
+} ConnectToServerData;
+
+static void
+connect_to_server_data_free (ConnectToServerData *data)
+{
+  g_clear_object (&amp;data->connection);
+}
+
+void
+my_object_connect_to_server_async (MyObject            *self,
+                                   GCancellable        *cancellable,
+                                   GAsyncReadyCallback  callback,
+                                   gpointer             user_data)
+{
+  MyObjectPrivate *priv;
+  GTask *task = NULL;  /* owned */
+  ConnectToServerData *data = NULL;  /* owned */
+  GFile *address_file = NULL;  /* owned */
+
+  g_return_if_fail (MY_IS_OBJECT (self));
+  g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+  priv = my_object_get_instance_private (self);
+
+  if (priv->connect_task != NULL)
+    {
+      g_task_report_new_error (self, callback, user_data, NULL,
+                               G_IO_ERROR, G_IO_ERROR_PENDING,
+                               "Already connecting to the server.");
+      return;
+    }
+
+  /* Set up a cancellable. */
+  if (cancellable != NULL)
+    {
+      g_object_ref (cancellable);
+    }
+  else
+    {
+      cancellable = g_cancellable_new ();
+    }
+
+  /* Set up the task. */
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_check_cancellable (task, FALSE);
+
+  data = g_malloc0 (sizeof (ConnectToServerData));
+  g_task_set_task_data (task, data,
+                        (GDestroyNotify) connect_to_server_data_free);
+
+  g_object_unref (cancellable);
+
+  priv->connect_task = g_object_ref (task);
+
+  /* Read the socket address. */
+  address_file = build_address_file ();
+  g_file_load_contents_async (address_file, g_task_get_cancellable (task),
+                              connect_to_server_cb1, g_object_ref (task));
+  g_object_unref (address_file);
+
+  g_clear_object (&amp;task);
+}
+
+static void
+connect_to_server_cb1 (GObject      *source_object,
+                       GAsyncResult *result,
+                       gpointer      user_data)
+{
+  MyObject *self;
+  MyObjectPrivate *priv;
+  GTask *task = NULL;  /* owned */
+  GFile *address_file;  /* unowned */
+  gchar *address = NULL;  /* owned */
+  gsize address_size = 0;
+  GInetAddress *inet_address = NULL;  /* owned */
+  GInetSocketAddress *inet_socket_address = NULL;  /* owned */
+  guint16 port = 123;
+  GSocketClient *socket_client = NULL;  /* owned */
+  GError *error = NULL;
+
+  address_file = G_FILE (source_object);
+  task = G_TASK (user_data);
+  self = g_task_get_source_object (task);
+  priv = my_object_get_instance_private (self);
+
+  /* Finish loading the address. */
+  g_file_load_contents_finish (address_file, result, &amp;address,
+                               &amp;address_size, NULL, &amp;error);
+
+  if (error != NULL)
+    {
+      goto done;
+    }
+
+  /* Parse the address. */
+  inet_address = g_inet_address_new_from_string (address);
+
+  if (inet_address == NULL)
+    {
+      /* Error. */
+      g_set_error (&amp;error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+                   "Invalid address ‘%s’.", address);
+      goto done;
+    }
+
+  inet_socket_address = g_inet_socket_address_new (inet_address, port);
+
+  /* Connect to the given address. */
+  socket_client = g_socket_client_new ();
+
+  g_socket_client_connect_async (socket_client,
+                                 G_SOCKET_CONNECTABLE (inet_socket_address),
+                                 g_task_get_cancellable (task),
+                                 connect_to_server_cb2,
+                                 g_object_ref (task));
+
+done:
+  if (error != NULL)
+    {
+      /* Stop the operation and propagate the error. */
+      g_clear_object (&amp;priv->connect_task);
+      g_task_return_error (task, error);
+    }
+
+  g_free (address);
+  g_clear_object (&amp;inet_address);
+  g_clear_object (&amp;inet_socket_address);
+  g_clear_object (&amp;socket_client);
+  g_clear_object (&amp;task);
+}
+
+static void
+connect_to_server_cb2 (GObject      *source_object,
+                       GAsyncResult *result,
+                       gpointer      user_data)
+{
+  MyObject *self;
+  MyObjectPrivate *priv;
+  GTask *task = NULL;  /* owned */
+  ConnectToServerData *data;  /* unowned */
+  GSocketClient *socket_client;  /* unowned */
+  GSocketConnection *connection = NULL;  /* owned */
+  GInputStream *input_stream;  /* unowned */
+  GError *error = NULL;
+
+  socket_client = G_SOCKET_CLIENT (source_object);
+  task = G_TASK (user_data);
+  data = g_task_get_task_data (task);
+  self = g_task_get_source_object (task);
+  priv = my_object_get_instance_private (self);
+
+  /* Finish connecting to the socket. */
+  connection = g_socket_client_connect_finish (socket_client, result,
+                                               &amp;error);
+
+  if (error != NULL)
+    {
+      goto done;
+    }
+
+  /* Store a reference to the connection so it is kept open while we read from
+   * it: #GInputStream does not keep a reference to a #GIOStream which contains
+   * it. */
+  data->connection = g_object_ref (connection);
+
+  /* Read a message from the connection. As the buffer is allocated as part of
+   * the per-task @data, multiple tasks can run concurrently. */
+  input_stream = g_io_stream_get_input_stream (G_IO_STREAM (connection));
+
+  g_input_stream_read_async (input_stream,
+                             data->message_buffer,
+                             sizeof (data->message_buffer),
+                             G_PRIORITY_DEFAULT, g_task_get_cancellable (task),
+                             connect_to_server_cb3, g_object_ref (task));
+
+done:
+  if (error != NULL)
+    {
+      /* Stop the operation and propagate the error. */
+      g_clear_object (&amp;priv->connect_task);
+      g_task_return_error (task, error);
+    }
+
+  g_clear_object (&amp;connection);
+  g_clear_object (&amp;task);
+}
+
+static void
+connect_to_server_cb3 (GObject      *source_object,
+                       GAsyncResult *result,
+                       gpointer      user_data)
+{
+  MyObject *self;
+  MyObjectPrivate *priv;
+  GTask *task = NULL;  /* owned */
+  ConnectToServerData *data;  /* unowned */
+  GInputStream *input_stream;  /* unowned */
+  gssize len = 0;
+  GError *error = NULL;
+
+  input_stream = G_INPUT_STREAM (source_object);
+  task = G_TASK (user_data);
+  data = g_task_get_task_data (task);
+  self = g_task_get_source_object (task);
+  priv = my_object_get_instance_private (self);
+
+  /* Finish reading from the socket. */
+  len = g_input_stream_read_finish (input_stream, result, &amp;error);
+
+  if (error != NULL)
+    {
+      goto done;
+    }
+
+  /* Handle the message. */
+  g_assert_cmpint (len, >=, 0);
+  g_assert_cmpuint ((gsize) len, &lt;=, sizeof (data->message_buffer));
+
+  handle_received_message (self, data->message_buffer, len, &amp;error);
+
+  if (error != NULL)
+    {
+      goto done;
+    }
+
+  /* Success! */
+  g_task_return_boolean (task, TRUE);
+
+done:
+  /* Unconditionally mark the operation as finished.
+   *
+   * The streams should automatically close as this
+   * last reference is dropped. */
+  g_clear_object (&amp;priv->connect_task);
+
+  if (error != NULL)
+    {
+      /* Stop the operation and propagate the error. */
+      g_task_return_error (task, error);
+    }
+
+  g_clear_object (&amp;task);
+}
+
+void
+my_object_connect_to_server_finish (MyObject      *self,
+                                    GAsyncResult  *result,
+                                    GError       **error)
+{
+  g_return_if_fail (MY_IS_OBJECT (self));
+  g_return_if_fail (g_task_is_valid (result, self));
+  g_return_if_fail (error == NULL || *error == NULL);
+
+  g_task_propagate_boolean (G_TASK (result), error);
+}
+
+void
+my_object_close (MyObject *self)
+{
+  MyObjectPrivate *priv;
+
+  g_return_if_fail (MY_IS_OBJECT (self));
+
+  priv = my_object_get_instance_private (self);
+
+  if (priv->connect_task != NULL)
+    {
+      GCancellable *cancellable = g_task_get_cancellable (priv->connect_task);
+      g_cancellable_cancel (cancellable);
+    }
+}</code>
+      </example>
+    </section>
+  </section>
+</page>
diff --git a/programming-guidelines/C/main-contexts.page b/programming-guidelines/C/main-contexts.page
index 2bc60afa..441bc0fa 100644
--- a/programming-guidelines/C/main-contexts.page
+++ b/programming-guidelines/C/main-contexts.page
@@ -466,14 +466,18 @@ do_computation (gpointer user_data)
     </p>
 
     <p>
-      Always write things asynchronously internally (using
+      <link xref="async-programming">Write things asynchronously</link>
+      internally (using
       <link xref="#gtask"><code>GTask</code></link> where appropriate), and keep
       synchronous wrappers at the very top level of an API, where they can be
       implemented by calling <code>g_main_context_iteration()</code> on a
       specific <code>GMainContext</code>. Again, this makes future refactoring
       easier. This is demonstrated in the above example: the thread uses
       <code>g_output_stream_write_async()</code> rather than
-      <code>g_output_stream_write()</code>.
+      <code>g_output_stream_write()</code>. A worker thread may be used instead,
+      and this can simplify the callback chain for long series of asynchronous
+      calls; but at the cost of increased complexity in verifying the code is
+      race-free.
     </p>
 
     <p>
diff --git a/programming-guidelines/C/threading.page b/programming-guidelines/C/threading.page
index 61168e58..2e9498ff 100644
--- a/programming-guidelines/C/threading.page
+++ b/programming-guidelines/C/threading.page
@@ -55,7 +55,7 @@
       When writing projects using GLib, the default approach should be to
       <em style="strong">never use threads</em>. Instead, make proper use of the
       <link xref="main-contexts">GLib main context</link> which, through the use
-      of asynchronous operations,
+      of <link xref="async-programming">asynchronous operations</link>,
       allows most blocking I/O operations to continue in the background while
       the main context continues to process other events. Analysis, review and
       debugging of threaded code becomes very hard, very quickly.
diff --git a/programming-guidelines/Makefile.am b/programming-guidelines/Makefile.am
index 5b2bf330..affd1067 100644
--- a/programming-guidelines/Makefile.am
+++ b/programming-guidelines/Makefile.am
@@ -9,6 +9,7 @@ HELP_EXTRA = \
 HELP_FILES = \
        additional-materials.page \
        api-stability.page \
+       async-programming.page \
        c-coding-style.page \
        cc-by-sa-3-0.xml \
        databases.page \



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