Async
Async/await is a way to run two or more functions at the same time. It’s often used for file and network I/O.
To get started, we’ll run two tasks without async:
const std = @import("std");
const print = std.debug.print;
// For the examples here we’ll simulate a slow operation
// by calling nanosleep(2).
fn sleep(ms: isize) void {
var ts: std.posix.timespec =
.{ .sec = 0, .nsec = ms * 1000000 };
_ = std.posix.system.nanosleep(&ts, &ts);
}
fn task(count: u64, label: []const u8) void {
for (0..count) |_| {
print("{s}\n", .{label});
sleep(100);
}
}
pub fn main() void {
task(4, "aaa");
task(4, " bbb");
}As expected, the tasks run one after the other:
$ zig run async.zig
aaa
aaa
aaa
aaa
bbb
bbb
bbb
bbb
Running two tasks at the same time
Let’s use async/await to run the tasks at the same time:
const std = @import("std");
const print = std.debug.print;
fn sleep(ms: isize) void {
var ts: std.posix.timespec =
.{ .sec = 0, .nsec = ms * 1000000 };
_ = std.posix.system.nanosleep(&ts, &ts);
}
fn task(count: u64, label: []const u8) void {
for (0..count) |_| {
print("{s}\n", .{label});
sleep(100);
}
}
fn run(io: std.Io) void {
// Io.async starts a task and returns a Future.
var a = io.async(task, .{ 4, "aaa" });
var b = io.async(task, .{ 4, " bbb" });
// We call 'await' to wait for the task to finish.
a.await(io);
b.await(io);
}
pub fn main(init: std.process.Init) void {
run(init.io);
}The tasks are running at the same time now:
$ zig run async-2.zig
bbb
aaa
bbb
aaa
aaa
bbb
aaa
bbb
Returning a value
If a task returns a value, we can get it from await:
const std = @import("std");
const print = std.debug.print;
fn sleep(ms: isize) void {
var ts: std.posix.timespec =
.{ .sec = 0, .nsec = ms * 1000000 };
_ = std.posix.system.nanosleep(&ts, &ts);
}
fn task(count: u64, label: []const u8, res: u64) u64 {
for (0..count) |_| {
print("{s}\n", .{label});
sleep(100);
}
return res;
}
fn run(io: std.Io) void {
var a = io.async(task, .{ 4, "aaa", 1 });
var b = io.async(task, .{ 4, " bbb", 2 });
const result_a = a.await(io);
const result_b = b.await(io);
print("result of task a: {d}\n", .{result_a});
print("result of task b: {d}\n", .{result_b});
}
pub fn main(init: std.process.Init) void {
run(init.io);
}$ zig run async-3.zig
bbb
aaa
bbb
aaa
aaa
bbb
aaa
bbb
result of task a: 1
result of task b: 2
Returning an error
If the async function returns an error,
await returns the error and we can handle it as usual.
However, we have to make sure we await each task even in the error
case. The conventional way to do that is to always defer a call to
cancel.
await and cancel are idempotent, so it’s OK
to call cancel when we’ve already called
await.
const std = @import("std");
const print = std.debug.print;
fn sleep(ms: isize) void {
var ts: std.posix.timespec =
.{ .sec = 0, .nsec = ms * 1000000 };
_ = std.posix.system.nanosleep(&ts, &ts);
}
// If 'fail' is true, 'task' returns an error after half
// the steps.
fn task(
count: u64,
label: []const u8,
fail: bool,
) !void {
for (0..count) |i| {
if (fail and i == count / 2)
return error.TaskFailed;
print("{s}\n", .{label});
sleep(100);
}
}
fn run(io: std.Io) !void {
var a = io.async(task, .{ 4, "aaa", true });
defer a.cancel(io) catch {};
var b = io.async(task, .{ 4, " bbb", false });
defer b.cancel(io) catch {};
try a.await(io);
try b.await(io);
}
pub fn main(init: std.process.Init) void {
run(init.io) catch |e| {
print("error: {}\n", .{e});
};
}$ zig run async-4.zig
bbb
aaa
bbb
aaa
bbb
error: error.TaskFailed
Cancel a task
We can also use cancel to actually cancel a task:
const std = @import("std");
const print = std.debug.print;
fn sleep(ms: isize) void {
var ts: std.posix.timespec =
.{ .sec = 0, .nsec = ms * 1000000 };
_ = std.posix.system.nanosleep(&ts, &ts);
}
fn task(count: u64, label: []const u8) void {
for (0..count) |_| {
print("{s}\n", .{label});
sleep(100);
}
}
fn run(io: std.Io) void {
// Start the task, wait a bit, then cancel it
// half-way through.
var a = io.async(task, .{ 4, "aaa" });
defer a.cancel(io);
sleep(150);
}
pub fn main(init: std.process.Init) void {
run(init.io);
}$ zig run async-5.zig
aaa
aaa
Next example: Io Implementations.