|
| 1 | +# Async procedures |
| 2 | + |
| 3 | +Async procedures are those that interact with `chronos` to cooperatively |
| 4 | +suspend and resume their execution depending on the completion of other |
| 5 | +async procedures, timers, tasks on other threads or asynchronous I/O scheduled |
| 6 | +with the operating system. |
| 7 | + |
| 8 | +Async procedures are marked with the `{.async.}` pragma and return a `Future` |
| 9 | +indicating the state of the operation. |
| 10 | + |
| 11 | +<!-- toc --> |
| 12 | + |
| 13 | +## The `async` pragma |
| 14 | + |
| 15 | +The `{.async.}` pragma will transform a procedure (or a method) returning a |
| 16 | +`Future` into a closure iterator. If there is no return type specified, |
| 17 | +`Future[void]` is returned. |
| 18 | + |
| 19 | +```nim |
| 20 | +proc p() {.async.} = |
| 21 | + await sleepAsync(100.milliseconds) |
| 22 | +
|
| 23 | +echo p().type # prints "Future[system.void]" |
| 24 | +``` |
| 25 | + |
| 26 | +## `await` keyword |
| 27 | + |
| 28 | +The `await` keyword operates on `Future` instances typically returned from an |
| 29 | +`async` procedure. |
| 30 | + |
| 31 | +Whenever `await` is encountered inside an async procedure, control is given |
| 32 | +back to the dispatcher for as many steps as it's necessary for the awaited |
| 33 | +future to complete, fail or be cancelled. `await` calls the |
| 34 | +equivalent of `Future.read()` on the completed future to return the |
| 35 | +encapsulated value when the operation finishes. |
| 36 | + |
| 37 | +```nim |
| 38 | +proc p1() {.async.} = |
| 39 | + await sleepAsync(1.seconds) |
| 40 | +
|
| 41 | +proc p2() {.async.} = |
| 42 | + await sleepAsync(1.seconds) |
| 43 | +
|
| 44 | +proc p3() {.async.} = |
| 45 | + let |
| 46 | + fut1 = p1() |
| 47 | + fut2 = p2() |
| 48 | + # Just by executing the async procs, both resulting futures entered the |
| 49 | + # dispatcher queue and their "clocks" started ticking. |
| 50 | + await fut1 |
| 51 | + await fut2 |
| 52 | + # Only one second passed while awaiting them both, not two. |
| 53 | +
|
| 54 | +waitFor p3() |
| 55 | +``` |
| 56 | + |
| 57 | +```admonition warning |
| 58 | +Because `async` procedures are executed concurrently, they are subject to many |
| 59 | +of the same risks that typically accompany multithreaded programming. |
| 60 | +
|
| 61 | +In particular, if two `async` procedures have access to the same mutable state, |
| 62 | +the value before and after `await` might not be the same as the order of execution is not guaranteed! |
| 63 | +``` |
| 64 | + |
| 65 | +## Raw async procedures |
| 66 | + |
| 67 | +Raw async procedures are those that interact with `chronos` via the `Future` |
| 68 | +type but whose body does not go through the async transformation. |
| 69 | + |
| 70 | +Such functions are created by adding `raw: true` to the `async` parameters: |
| 71 | + |
| 72 | +```nim |
| 73 | +proc rawAsync(): Future[void] {.async: (raw: true).} = |
| 74 | + let fut = newFuture[void]("rawAsync") |
| 75 | + fut.complete() |
| 76 | + fut |
| 77 | +``` |
| 78 | + |
| 79 | +Raw functions must not raise exceptions directly - they are implicitly declared |
| 80 | +as `raises: []` - instead they should store exceptions in the returned `Future`: |
| 81 | + |
| 82 | +```nim |
| 83 | +proc rawFailure(): Future[void] {.async: (raw: true).} = |
| 84 | + let fut = newFuture[void]("rawAsync") |
| 85 | + fut.fail((ref ValueError)(msg: "Oh no!")) |
| 86 | + fut |
| 87 | +``` |
| 88 | + |
| 89 | +Raw procedures can also use checked exceptions: |
| 90 | + |
| 91 | +```nim |
| 92 | +proc rawAsyncRaises(): Future[void] {.async: (raw: true, raises: [IOError]).} = |
| 93 | + let fut = newFuture[void]() |
| 94 | + assert not (compiles do: fut.fail((ref ValueError)(msg: "uh-uh"))) |
| 95 | + fut.fail((ref IOError)(msg: "IO")) |
| 96 | + fut |
| 97 | +``` |
| 98 | + |
| 99 | +## Callbacks and closures |
| 100 | + |
| 101 | +Callback/closure types are declared using the `async` annotation as usual: |
| 102 | + |
| 103 | +```nim |
| 104 | +type MyCallback = proc(): Future[void] {.async.} |
| 105 | +
|
| 106 | +proc runCallback(cb: MyCallback) {.async: (raises: []).} = |
| 107 | + try: |
| 108 | + await cb() |
| 109 | + except CatchableError: |
| 110 | + discard # handle errors as usual |
| 111 | +``` |
| 112 | + |
| 113 | +When calling a callback, it is important to remember that it may raise exceptions that need to be handled. |
| 114 | + |
| 115 | +Checked exceptions can be used to limit the exceptions that a callback can |
| 116 | +raise: |
| 117 | + |
| 118 | +```nim |
| 119 | +type MyEasyCallback = proc(): Future[void] {.async: (raises: []).} |
| 120 | +
|
| 121 | +proc runCallback(cb: MyEasyCallback) {.async: (raises: [])} = |
| 122 | + await cb() |
| 123 | +``` |
0 commit comments