4.8. Asynchronous Methods

Asynchronous methods are methods whose execution can be paused and resumed under the control of the programmer. They are often used in the main thread of an application where a method needs to wait for an external slow task to complete, but must not stop other processing from happening. (For example, one slow operation must not freeze the whole GUI). When the method has to wait, it gives control of the CPU back to its caller (i.e. it yields), but it arranges to be called back to resume execution when data becomes ready. External slow tasks that async methods might wait for include: waiting for data from a remote server, or waiting for calculations in another thread to complete, or waiting for data to load from a disk drive.

Asynchronous methods are normally used with a GLib main loop running, because idle callbacks are used to handle some of the internal callbacks. However under certain conditions async may be used without the GLib main loop, for example if the async methods always yield and Idle.add() is never used.

Todo

Add the exact conditions async may be used without the GLib main loop

Asynchronous methods are designed for interleaving the processing of many different long-lived operations within a single thread. They do not by themselves spread the load out over different threads. However, an async method may be used to control a background thread and to wait for it to complete, or to queue operations for a background thread to process.

Async methods in Vala use the GIO library to handle the callbacks, so must be built with the --pkg=gio-2.0 option.

An asynchronous method is defined with the async keyword. For example:

async void display_jpeg(string fnam) {
    // Load JPEG in a background thread and display it when loaded
    [...]
}

or:

async int fetch_webpage(string url, out string text) throws IOError {
    // Fetch a webpage asynchronously and when ready return the
    // HTTP status code and put the page contents in 'text'
    [...]
    text = result;
    return status;
}

The method may take arguments and return a value like any other method. It may use a yield statement at any time to give control of the CPU back to its caller.

An async method may be called with either of these two forms:

display_jpeg.begin("test.jpg");
display_jpeg.begin("test.jpg", (obj, res) => {
    display_jpeg.end(res);
});

Both forms starts the async method running with the given arguments. The second form in addition registers an !AsyncReadyCallback which is executed when the method finishes. The callback takes a source object, obj, and an instance of GAyncResult, res, as arguments. In the callback the .end() method should be called to receive the return value of the asynchronous method if it has one. If the async method can throw an exception, the .end() call is where the exception arrives and must be caught. If the method has out arguments, then these should be omitted from the .begin() call and added to the .end() call instead.

For example:

fetch_webpage.begin("http://www.example.com/", (obj, res) => {
    try {
        string text;
        var status = fetch_webpage.end(res, out text);
        // Result of call is in 'text' and 'status' ...
    } catch (IOError e) {
        // Problem ...
    }
});

When an asynchronous method starts running, it takes control of the CPU until it reaches its first yield statement, at which point it returns to the caller. When the method is resumed, it continues execution immediately after that yield statement. There are several common ways to use yield:

This form gives up control, but arranges for the GLib main loop to resume the method when there are no more events to process:

Idle.add(fetch_webpage.callback);
yield;

This form gives up control, and stores the callback details for some other code to use to resume the method’s execution:

SourceFunc callback = fetch_webpage.callback;
[... store 'callback' somewhere ...]
yield;

Some code elsewhere must now call the stored !SourceFunc in order for the method to be resumed. This could be done by scheduling the GLib main loop to run it:

Idle.add((owned) callback);

or alternatively a direct call may be made if the caller is running in the main thread:

callback();

If the direct call above is used, then the resumed asynchronous method takes control of the CPU immediately and runs until its next yield before returning to the code that executed callback(). The Idle.add() method is useful if the callback must be made from a background thread, e.g. to resume the async method after completion of some background processing. (The (owned) cast is necessary to avoid a warning about copying delegates.)

The third common way of using yield is when calling another asynchronous method, for example:

yield display_jpeg(fnam);

or

var status = yield fetch_webpage(url, out text);

In both cases, the calling method gives up control of the CPU and does not resume until the called method completes. The yield statement automatically registers a callback with the called method to make sure that the caller resumes correctly. The automatic callback also collects the return value from the called method.

When this yield statement executes, control of the CPU first passes to the called method which runs until its first yield and then drops back to the calling method, which completes the yield statement itself, and then gives back control to its own caller.

4.8.1. Examples

See Async Method Samples for examples of different ways that async may be used.