-
Notifications
You must be signed in to change notification settings - Fork 177
Create eventqueue-background.md #507
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,206 @@ | ||
| ## The `EventQueue.background` method | ||
|
|
||
| An [event queue](/docs/development/tutorials/the-eventqueue-api.html) moves tasks from one **execution context** to another **execution context**. | ||
|
|
||
| In the embedded space, developers use the event queue to move IRQs from high-priority interrupt context to a low-priority threaded context: | ||
|
|
||
| ``` cpp | ||
| EventQueue equeue; | ||
|
|
||
| // example function | ||
| void print_callback() { | ||
| printf("hi!\n"); | ||
| } | ||
|
|
||
| // high priority interrupt context | ||
| void irq() { | ||
| // we use the "call" method to pass the callback_function to a different execution context | ||
| equeue.call(print_callback); | ||
| } | ||
|
|
||
| // low priority threaded context | ||
| int main() { | ||
| // hardware timer, can only execute code in interrupt context | ||
| Timer timer; | ||
| timer.attach_ms(irq, 100); | ||
|
|
||
| // dispatch events in our current context | ||
| equeue.dispatch(); | ||
| } | ||
| ``` | ||
|
|
||
| This example assigns the execution context for events by calling the `dispatch` function. `dispatch` runs the event queue in the current execution context, in this case the low-priority thread. | ||
|
|
||
| However, `dispatch` has a downside: It consumes the whole thread. While you're running `dispatch`, you can't execute anything else. | ||
|
|
||
| Passing a timeout to `dispatch` lets you get your execution context back for periodic events: | ||
|
|
||
| ``` cpp | ||
| // low priority threaded context | ||
| int main() { | ||
| // hardware timer, can only execute code in interrupt context | ||
| Timer timer; | ||
| timer.attach_ms(irq, 100); | ||
|
|
||
| while (true) { | ||
| // dispatch events in our current context for 100 ms | ||
| equeue.dispatch(100); | ||
|
|
||
| // super fancy status update system | ||
| led = !led; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| You can even pass 0 ms to execute any pending events and return immediately, which lets you spend all of your time blinking the LED. | ||
|
|
||
| However, that's still rather limiting. If you want to do stuff in your thread, execute events _and_ sleep, you need to learn how long to sleep from the event queue. However, because any IRQ can update the event queue, the time to sleep can change, so you also need a way to receive notifications when a new sleep time has been decided. | ||
|
|
||
| This is the use case for the event queue `background` function. | ||
|
|
||
| ``` cpp | ||
| /** Background an event queue onto a single-shot timer-interrupt | ||
| * | ||
| * When updated, the event queue calls the provided update function | ||
| * with a timeout indicating when the queue should be dispatched. A | ||
| * negative timeout is passed to the update function when the | ||
| * timer-interrupt is no longer needed. | ||
| * | ||
| * Passing a null function disables the existing update function. | ||
| * | ||
| * The background function allows an event queue to take advantage of | ||
| * hardware timers or other event loops, allowing an event queue to be | ||
| * run in the background without consuming the foreground thread. | ||
| * | ||
| * @param update Function called to indicate when the queue should be | ||
| * dispatched | ||
| */ | ||
| void background(mbed::Callback<void(int)> update); | ||
| ``` | ||
|
|
||
| The `background` function is a way of allowing the event queue to tell you when it can sleep. You can use `background` and `dispatch` together to run an event queue in the "background" of an execution context, without completely consuming the context. | ||
|
|
||
| This looks like the following: | ||
|
|
||
| ``` cpp | ||
| // sleeping stuff so we're not burning fuel all the time | ||
| int sleep_time = -1; | ||
| Semaphore sleep_sema; | ||
| void sleep_callback(int new_sleep_time) { | ||
| // update sleep_time | ||
| sleep_time = new_sleep_time; | ||
|
|
||
| // go ahead and wake up thread so it can sleep for new sleep_time | ||
| sleep_sema.signal(); | ||
| } | ||
|
|
||
| // low priority threaded context | ||
| int main() { | ||
| // hardware timer, can only execute code in interrupt context | ||
| Timer timer; | ||
| timer.attach_ms(irq, 100); | ||
|
|
||
| // attach our sleep callback so we get sleep updates | ||
| equeue.background(sleep_callback); | ||
|
|
||
| while (true) { | ||
| // dispatch any pending events | ||
| equeue.dispatch(0); | ||
|
|
||
| // super duper fancy status update system | ||
| led = !led; | ||
|
|
||
| // go to sleep until next event | ||
| sleep_sema.wait(sleep_time); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| Now you will execute events, toggle your LED and sleep all using just one execution context. | ||
|
|
||
| The `background` function handles all of the corner cases for you, such as canceling events and nested events, so as long as you call dispatch after the timeout, everything works. | ||
|
|
||
| <span class="notes">**Note:** When the event queue _doesn't_ have a timeout, the callback is passed the value `-1` (which is the same as `osWaitForever`). In this example, passing `-1` to the semaphor wait returns the behavior you want, but sometimes you need to treat this as a special case. Also, the event queue passes `-1` at destruction time.</span> | ||
|
|
||
| That while loop resembles an event queue by itself. Although you can use the `background` function to run one event queue in another event queue's context, it's complicated enough that it is wrapped up in `background`'s sister function: `chain`: | ||
|
|
||
| ``` cpp | ||
| /** Chain an event queue onto another event queue | ||
| * | ||
| * After chaining a queue to a target, calling dispatch on the target | ||
| * queue also dispatches events from this queue. The queues use | ||
| * its own buffers, and you must handle events independently. | ||
| * | ||
| * A null queue as the target unchains the existing queue. | ||
| * | ||
| * The chain function allows you to compose multiple event queues, | ||
| * sharing the context of a dispatch loop while still being managed | ||
| * independently. | ||
| * | ||
| * @param target Queue that dispatches this queue's events as a | ||
| * part of its dispatch loop | ||
| */ | ||
| void chain(EventQueue *target); | ||
| ``` | ||
|
|
||
| With `chain`, you can rewrite [a blinking light example](mbed-os-quick-start.html) to use its own event queue: | ||
|
|
||
| ``` cpp | ||
| // your own event queue | ||
| EventQueue blinky_equeue(2*EVENTS_EVENT_SIZE); | ||
|
|
||
| // status update | ||
| void blink() { | ||
| led = !led; | ||
| } | ||
|
|
||
| // low priority threaded context | ||
| int main() { | ||
| // hardware timer, can only execute code in interrupt context | ||
| Timer timer; | ||
| timer.attach_ms(irq, 100); | ||
|
|
||
| // chain your event queue onto this new event queue for the blinking lights | ||
| equeue.chain(&blinky_equeue); | ||
|
|
||
| // blink our led every 100 ms | ||
| blinky_equeue.call_every(100, blink); | ||
|
|
||
| // dispatch our root event queue | ||
| blinky_equeue.dispatch(); | ||
| } | ||
| ``` | ||
|
|
||
| You want to tightly control the memory consumption of each event loop. | ||
|
|
||
| Consider an example Sonar class driven by a complicated FSM that I'm too lazy to write: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could an engineer please create an example to replace this one? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Petition to replace this with a link to the sonar example as per our discussion @geky @AnotherButler There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @geky @AnotherButler Thoughts on the sonar example link? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds reasonable to me. That or we could just remove the sentence about laziness. The example does have the part that's important to users of the event queue. I'd remove it but I don't think I can push to this branch. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| ``` cpp | ||
| class Sonar { | ||
| Sonar(EventQueue *parent_queue=mbed_event_queue()) { | ||
| // attach our sonar FSM to the provided event queue, defaulting to the global mbed event queue | ||
| _equeue.chain(parent); | ||
| } | ||
|
|
||
| EventQueue _equeue(SONAR_EVENTS * EVENTS_EVENT_SIZE); | ||
| } | ||
| ``` | ||
|
|
||
| Normally, when you pass around an event queue, you need to track how many events could be allocated at once. However, if you chain event queues together, the parent no longer needs to care about the child's quantity of events, as long as it gives the child the hook to dispatch its own event queue (1 event for the child's chain call). | ||
|
|
||
| In this way, you can pass the decision of execution context entirely up to your caller: | ||
|
|
||
| ``` cpp | ||
| // create three sonars using your event queue | ||
| EventQueue queue(3 * EVENTS_EVENT_SIZE); | ||
| Sonar s1(&queue) | ||
| Sonar s2(&queue); | ||
| Sonar s3(&queue); | ||
|
|
||
| int main() { | ||
| // dispatch all of your sonars at once | ||
| queue.dispatch(); | ||
| } | ||
| ``` | ||
|
|
||
| <span class="notes">**Note:** This is a form of controlling execution context _explicitly_. In Mbed OS, you can also control execution context _implicitly_ by creating different threads for each sonar. The decision for which one to use is left up to you.</span> | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Query: Could we summarize lines 6-58 with "Performing concurrent functions with an event queue can become complicated. For example, to concurrently perform operations in a thread, execute events and sleep, you need to..."?
I ask because we're spending 50+ lines to show a user how not to solve a problem. User documentation should tell users what to do, not what not to do. Similarly, user documentation can tell users why we chose the features we did, but it shouldn't say why we didn't choose the features we didn't.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These examples build up the problem and also demonstrate alternative solutions that a user may choose if they don't have the exact problem we're demonstrating a solution for.
What if the user doesn't care about sleeping? Where do they go? Should they stop reading this tutorial?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is a tutorial about EventQueue.background, it should tell people what this is and how and when to use it. It should not give lots of solutions for lots of problems that aren't related to using this method.
When I read this, what I got out of it is that you need EventQueue.background if "you want to do stuff in your thread, execute events and sleep" because you need to learn how long to sleep from the event queue. Are there other use cases? Is this just a specific example of a much broader use? If so, it's not obvious.
That's why I'm trying to summarize a general use case for this method. That's why I'm asking if adding a sentence such as "Performing concurrent functions with an event queue can become complicated." would help. It doesn't make sense to spend 50 lines telling users how other solutions to a specific situation don't work instead of telling people when to use the method we're describing.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't match what I thought the tutorials are. Why would this documentation go here and not in the reference page?
Where do we put documentation that tells users how to solve problems?
I'm happy if this is the wrong place for that to go, but I want to know where that is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A tutorial about using the dispatch function should be a step-by-step guide about how and when to use the dispatch function. A tutorial about how to use dispatch without consuming the whole thread should be a step-by-step guide to using dispatch without consuming the whole thread and should talk about passing a timeout for 0 ms. A tutorial about EventQueue.background should be a step-by-step guide about how and when to use EventQueue.background. A tutorial about EventQueue.background should not be a guide about how to solve problems without using EventQueue.background. Does that make sense?
If the purpose of this tutorial is how to EventQueue.background, other information about how to solve unrelated problems without using EventQueue.background don't belong on this page. They could be great information we could include somewhere else, but it seems distracting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah so this tutorial should be renamed to "how to run event queues without extra threads"?
Considering it ends with "here's how you use EventQueue.chain" it sounds like this document doesn't fit here at all.
(I'm still confused how "how to EventQueue.background" is not the same as the reference page)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that works. I thought community developers were asking for how to use EventQueue.background, but if they're really asking how to run event queues without extra threads, then we can use that as a title and keep the beginning information here.