Introduction to Horse64 Concurrency

Horse64 has a built-in handing for async, or so-called concurrent functions. These usually depend on external I/O or networking.

Basic concurrent call

To avoid that any external resources stalling will freeze your program, functionality like disk or network access in Horse64 is concurrent. This means that while it happens, other code in your program can continue to run.

You call any concurrent functions annotated with the later keyword, indicating a time skip, see this call example:

import net.fetch from core.horse64.org
func main {
    var contents = net.fetch.get_str(
        "https://horse64.org"
    ) later:  # Execution after this runs later, after a time skip.

    await contents  # Access the results of your async call.
    print("Obtained website contents: " + contents)
}

Concurrent functions in Horse64 are also called "later functions". During the time skip, any other code can still run which will make any other interleaved executions resume smoothly.

The await keyword extracts the results, and causes errors to bubble up that happened in the call, if any. Here is how you catch errors after the time skip:

import net.fetch from core.horse64.org

func main {
   var contents = net.fetch.get_str(
       "https://horse64.org"
   ) later:  # Execution after this runs later, after a time skip.

   do {
       await contents  # Errors bubble up here.
       print("Obtained website contents: " + contents)
   } rescue NetworkIOError {
       print("Oops, our download failed!")
   }
}

later repeat

Since later calls aren't supported by horsec inside loops, like any for or while loop, whenever you need to call later functions in some repeating block, use later repeat instead in a pair like this:

Note: the repeat call at the bottom can be passed different arguments whenever that comes in handy!

import my_line_fetch

func main {
    var line = my_line_fetch.next_line(
        "https://horse64.org"
    ) later:

    await line
    print("Here's another line: " + line)

    line = my_line_fetch.next_line(
        "https://horse64.org"
    ) later repeat  # Jumps back up to right after `later:`
}

Note: both calls of such a later repeat pair must assign to the same variable.

with ... later

To use a concurrently created resource inside a with statement, instead of with create_my_resource_non_concurrent() as my_name { ... } use with create_my_resource_concurrent() later as my_name { ... }.

This is needed for all cases where the create function is a later function and hence it can only be called concurrently.

(You can always call normal functions with later as well, but a later function can't be called without later.)

Running code in parallel

⚠️⚠️ Using any of this without care might break your program. See the notes below.

Concurrently call multiple functions

The real gain from concurrency comes from running multiple things at a time, which will run them in parallel:

import net.fetch from core.horse64.org
import time from core.horse64.org

func parallel_counter {
    print("This is my parallel logic!")
    var i = 1
    while i < 10 {
        print("Tick")
        time.sleep(0.5)
        i += 1
    }
    return i
}

func main {
   var contents, count = net.fetch.get_str(
       "https://horse64.org"
   ), parallel_counter()
   later:  # Execution after this runs later, after a time skip.

   do {
       await contents, count  # Errors bubble up here.
       print("Obtained website contents: " + contents)
   } rescue NetworkIOError {
       print("Oops, our download failed!")
   }
}

Note: Running any of your funcs via true parallelism can expose ⚠️ dangerous race conditions in your code. If you are beginner, it's best to avoid this functionality.

later ignore

Don't want to wait? If you don't care about a later function's return value or its success, you can follow the call up with later ignore. This will make them run in the background in parallel as well:

import net.fetch from core.horse64.org

func main {
    net.fetch.get_str(
       "https://horse64.org"
    ) later ignore  # <- We don't care about success or return value.

    print("The internet fetch is likely still in progress now,
          but we don't care.")
}

In this case, the execution won't be delayed until the later call fully completes but instead continue without a possibly long time skip.

Note: Running any of your funcs via true parallelism can expose ⚠️ dangerous race conditions in your code. If you are beginner, it's best to avoid this functionality.

Further reading

For the formal calling rules, go here.

For avoiding ⚠️ dangerous race conditions if you ever run things in parallel, read here.

For the full formal concurrency model, go here.

Privacy | Admin Contact | Misc