-
-
Notifications
You must be signed in to change notification settings - Fork 84
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
Fix trasient typo; make general syntax and punctuation edits to readme #235
Conversation
@ColinDKelley Thanks for this big edit! Very nice formatting, and grammar improvements improve the clarity of the text. |
@@ -124,7 +124,7 @@ end | |||
|
|||
## Waiting for Tasks | |||
|
|||
Waiting for a single task is trivial, simply invoke {ruby Async::Task#wait}. To wait for multiple tasks, you may want to use a {ruby Async::Barrier}. You can use {ruby Async::Barrier#async} to create multiple child tasks, and wait for them all to complete using {ruby Async::Barrier#wait}. | |||
Waiting for a single task is trivial: simply invoke {ruby Async::Task#wait}. To wait for multiple tasks, you may either {ruby Async::Task#wait} on each in turn, or you may want to use a {ruby Async::Barrier}. You can use {ruby Async::Barrier#async} to create multiple child tasks, and wait for them all to complete using {ruby Async::Barrier#wait}. |
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.
We could still use some more guidance here about which is preferred and why.
We noticed that Async::Barrier#wait
was less convenient because it didn't allow us access to the final values the way Async::Task#wait
does. But we did find that Async::Barrier#wait
performed a bit better in our use case. (We still need to boil this down to a stand-alone example.)
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.
You can still call wait
on individual tasks if required after calling Barrier#wait
.
Depending on your use case, you should only follow that model if you are doing map-reduce. Explicit fan-out only to do a linear map of the results is a bit pointless - better to move all the mapping logic to the tasks themselves for the best concurrency.
I think we can elaborate more in the barrier.md
documentation.
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.
Ok, no changes in the follow-up here.
@@ -222,7 +222,7 @@ Async do | |||
end | |||
``` | |||
|
|||
If you're letting individual tasks held by a barrier throw unhandled exceptions, be sure to call ({ruby Async::Barrier#stop}): | |||
If you're letting individual tasks held by a barrier raise unhandled exceptions, be sure to call ({ruby Async::Barrier#stop}) to stop the remaining tasks: |
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.
I didn't follow the "letting" part at first. I think I do now, so I can take a shot at clarification.
I think we can assume it's normal to let unhandled exceptions out. Especially SystemError
s like SyntaxError
that won't be rescued by a plain rescue => ex
.
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.
I added some clarification in the follow-up.
@@ -364,21 +366,23 @@ require 'async' | |||
require 'thread/local' # thread-local gem. | |||
|
|||
class TimeCache | |||
extend Thread::Local | |||
extend Thread::Local # defines `instance` class method that lazy-creates a separate instance per thread |
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.
I found the use of this gem was distracting from the example. Maybe we can think up a different example that doesn't have the thread-local complexity?
Or, alternatively, if thread safety is critical topic to address, maybe a separate section just for that point?
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.
It's not possible to share tasks globally, so we need a per-thread cache (which is actually the best from a contention point-of-view). Maybe there is a better way we can demonstrate it.
|
||
def initialize | ||
@current_time = nil | ||
end | ||
|
||
def current_time | ||
def current_time_string |
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.
I had to read this a couple times to get the point that that we were caching the string and hence only needed 1-second granularity.
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.
Maybe we can add that as a comment.
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.
Updated in the lead-in paragraph in the follow-up.
@refresh ||= Async(transient: true) do | ||
while true | ||
loop do | ||
@current_time = Time.now.to_s | ||
sleep(1) | ||
end |
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.
Re: the comment just below, I don't follow how an exception will get raised. I thought that would require us to call .wait
on the task. Given that we're not doing that, doesn't Async(transient: true) do ... end
return immediately without any chance of raising an exception when the reactor terminates all tasks?
And BTW, do you mean "terminates all transient tasks" there?
But also, I'm missing the significance of storing nil
into the @refresh
variable. The rest of the class doesn't use that. So the only effect would be if refresh!
were called again, right? Was that the point?
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.
The refresh!
task should exist while the reactor exists, if the user calls a top level Async{TimeCache.current_time_string}
after exiting the top level block, the task created in refresh!
will exit. We want to ensure that the next time refresh!
is called, the task is recreated. Hence, on exiting the task, we ensure the instance variable is cleared.
Co-authored-by: Olle Jonsson <olle.jonsson@gmail.com>
Co-authored-by: Olle Jonsson <olle.jonsson@gmail.com>
@olleolleolle You're welcome! My team has just been converting a whole service over from |
|
||
There are various situations where you may want to stop a task ({ruby Async::Task#stop}). The most common case is shutting down a server, but other important situations exist, e.g. you may fan out multiple (10s, 100s) of requests, wait for a subset to complete (e.g. the first 5 or all those that complete within a given deadline), and then stop (terminate/cancel) the remaining operations. | ||
There are various situations where you may want to stop a task ({ruby Async::Task#stop}) before it completes. The most common case is shutting down a server, but other important situations exist, e.g. you may fan out multiple (10s, 100s) of requests, wait for a subset to complete (e.g. the first 5 or all those that complete within a given deadline), and then stop (terminate/cancel) the remaining operations. | ||
|
||
Using the above program as an example, we can |
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.
Also stopped half way through a sentence lol.
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.
I took a shot at finishing the sentence in the follow-up. :)
end | ||
``` | ||
|
||
Upon existing the top level async block, the {ruby @refresh} task will be set to `nil`. Bear in mind, you should not share these resources across threads, doing so would need further locking/coordination. | ||
Upon existing the top level async block, the {ruby @refresh} task will be set to `nil`. Bear in mind, you should not share these resources across threads; doing so would need some form of mutual exclusion. |
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.
Upon existing the top level async block, the {ruby @refresh} task will be set to `nil`. Bear in mind, you should not share these resources across threads; doing so would need some form of mutual exclusion. | |
Upon exiting the top level async block, the {ruby @refresh} task will be set to `nil`. Bear in mind, you should not share these resources across threads; doing so would need some form of mutual exclusion. |
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.
Fixed in follow-up.
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 is such a great improvement, I'll merge, it, and if you feel inclined to do a 2nd round of improvements, you have feedback.
Absolutely! I'll work on another PR tomorrow. |
Types of Changes
Contribution