1

The documentation in the "Tasks in Toit" section indicates that the language has facilities for asynchronous data exchange between tasks. If I understand correctly, then two classes from the monitor package: Channel and Mailbox provide this opportunity. Unfortunately, I didn't find examples of using these classes, so I ask you to give at least the simplest example of the implementation of two tasks:

  • One of the tasks is a message generator, for example, it sends integers or strings to the second task. The second task gets these numbers or strings. Perhaps in this case the Channel class should be used.
  • Each of the two tasks is both a generator and a receiver of messages. Those. the first task sends a message to the second task and in turn asynchronously receives the messages generated by the second task. Judging by the description, the Mailbox class should be used in this case.

Thanks in advance, MK

Lau
  • 23
  • 4
Michael Kanzieper
  • 709
  • 1
  • 10
  • 20

2 Answers2

2

Here's an example of the first part, using Channel. This class is useful if you have a stream of messages for another task.

import monitor

main:
  // A channel with a backlog of 5 items.  Once the reader is 5 items behind, the
  // writer will block when trying to send.  This helps avoid unbounded memory
  // use by the in-flight messages if messages are being generated faster than
  // they are being consumed.  Decreasing this will tend to reduce throughput,
  // increasing it will increase memory use.
  channel := monitor.Channel 5

  task:: message_generating_task channel
  task:: message_receiving_task channel

/// Normally this could be looking at IO ports, GPIO pins, or perhaps a socket
/// connection.  It could block for some unknown time while doing this.  In this
/// case we just sleep a little to illustrate that the data arrives at random
/// times.
generate_message:
  milliseconds := random 1000
  sleep --ms=milliseconds
  // The message is just a string, but could be any object.
  return "Message creation took $(milliseconds)ms"

message_generating_task channel/monitor.Channel:
  10.repeat:
    message := generate_message
    channel.send message
  channel.send null  // We are done.

/// Normally this could be looking at IO ports, GPIO pins, or perhaps a socket
/// connection.  It could block for some unknown time while doing this.  In this
/// case we just sleep a little to illustrate that the data takes a random
/// amount of time to process.
process_message message:
  milliseconds := random 1000
  sleep --ms=milliseconds
  print message

message_receiving_task channel/monitor.Channel:
  while message := channel.receive:
    process_message message

Here is an example of using Mailbox. This class is useful if you have a task processing requests and giving responses to other tasks.

import monitor

main:
  mailbox := monitor.Mailbox

  task:: client_task 1 mailbox
  task:: client_task 2 mailbox
  task --background:: factorization_task mailbox

/// Normally this could be looking at IO ports, GPIO pins, or perhaps a socket
/// connection.  It could block for some unknown time while doing this.  For
/// this example we just sleep a little to illustrate that the data arrives at
/// random times.
generate_huge_number:
  milliseconds := random 1000
  sleep --ms=milliseconds
  return (random 100) + 1  // Not actually so huge.

client_task task_number mailbox/monitor.Mailbox:
  10.repeat:
    huge := generate_huge_number
    factor := mailbox.send huge  // Send number, wait for result.
    other_factor := huge / factor
    print "task $task_number: $factor * $other_factor == $huge"

// Factorize a number using the quantum computing port.
factorize_number number:
  // TODO: Use actual quantum computing instead of brute-force search.
  for i := number.sqrt.round; i > 1; i--:
    factor := number / i
    if factor * i == number:
      return factor
    // This will yield so the other tasks can run.  In a real application it
    // would be waiting on an IO pin connected to the quantum computing unit.
    sleep --ms=1
  return 1  // 1 is sort-of a factor of all integers.

factorization_task mailbox/monitor.Mailbox:
  // Because this task was started as a background task (see 'main' function),
  // the program does not wait for it to exit so this loop does not need a real
  // exit condition.
  while number := mailbox.receive:
    result := factorize_number number
    mailbox.reply result
Florian Loitsch
  • 7,698
  • 25
  • 30
Erik Corry
  • 650
  • 4
  • 7
  • Thanks for the answer. He clarified a lot. One more question, if, of course, it does not greatly hardship you. I noticed that both in the tasks example in documentation and in your example, the sleep function is used to delay one task in order to allow the second task to "breathe" (let it run). This is a well-known technique, which, however, depends on the chosen time parameters. Is there any other time-independent mechanism in toit that ensures the asynchronous operation of individual application modules? In other words, is there any other facility besides the sleep function? – Michael Kanzieper Mar 28 '21 at 18:05
  • Tasks work cooperatively. When a task yields, another one is allowed to run. There are several ways a task can yield: `sleep` (as seen), `yield` (really just a "let others run, but come back to me asap"), or calling some system functions (like reading data from a socket). – Florian Loitsch Mar 29 '21 at 12:05
0

I'm pretty sure the Mailbox example worked great at the end of March. I decided to check it now and got the error:

  1. In case of Console Toit:

    ./web.toit:8:3: error: Argument mismatch: 'task' task --background:: factorization_task mailbox ^~~~ Compilation failed.

  2. In case of terminal:

    micrcx@micrcx-desktop:~/toit_apps/Hsm/communication$ toit execute mailbox_sample.toit mailbox_sample.toit:8:3: error: Argument mismatch: 'task' task --background:: factorization_task mailbox ^~~~ Compilation failed.

  3. Perhaps this is due to the latest SDK update. Just in case:

    Toit CLI: | v1.0.0 | 2021-03-29 |

Michael Kanzieper
  • 709
  • 1
  • 10
  • 20