-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Introduce typesafe actors abstraction #485
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
Conversation
counter++ | ||
} | ||
|
||
suspend fun counter(response: CompletableDeferred<Int>) = act { |
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.
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.
We discourage "send and wait" pattern as it's not an actor-ish way to do things, if you need it, you're probably using the wrong abstraction.
Actors come in a system, one actor is no actor. In the real world counter
method would send its result to another actor (which then updates UI, prints it to the console etc.).
Nevertheless, for some integrations with existing code it's more or less trivial to write actAndResponse
on top of act
in 5-10 lines of code. But we don't want to provide it in the library to avoid misusages
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.
Thanks for the quick response,
do you consider a good idea providing an example of bad code?
Should it be better describe your point of view in documentation and provide a better example?
|
||
val response = CompletableDeferred<Int>() | ||
counter.counter(response) | ||
println("Counter = ${response.await()}") |
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.
CompletableDeferred<Int>().also { counter.counter(it) }.await()
This looks like a code pattern
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 a matter of taste.
For me this pattern is obscure
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.
Agree. We should actually rewrite actor example in the guide. Actors should never be written like that. You should never need to wait for a response to a particular actor's action.
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.
Same example here
https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md#actors
Following your reasoning the "counter" use case fits really better using a Mutex
, otherwise you have to consider the feature of replying actor's messages.
I think I will add a short section. |
Is there a way to confine the current Actor implementation to say, Android's Service lifecycle? And also how would notifying progress to the UI be handled? I've tried with the previous implementation, and had code riddled with nested CompletableDeferreds, just to keep the service alive (by blocking its thread) while the actors were finishing their job... I didn't end up using the design because of the many hard to track down bugs that introduced... |
@dave08 |
@qwwdfsad I want Android to know when the actors are actually finished. If the actors are called on the main Service thread and don't block it, Android will think the Service finished its work. On the other hand, I need to fan out and fan in for different actor tasks. The main overseeing task should be reporting progress in a blocking way, until finished. So I had tried passing CompletableDeferreds to the actor chain, and await for them on the Services main thread, which blocks it and gives me progress... but it was a mess... Your point of using a Job is a different aspect, say if the Service must be interrupted even though it wasn't finished, then resources should be released etc... but I need that a regular run should complete normally. |
Could you extract your problem to a toy android app with a couple of classes? If so, I can rewrite it using actors (and probably will find a couple of bugs or API inconveniences) and add it as a sample project to the guide, some background job + UI progress bar + termination conditions are a perfect fit for actors and it would be great to have such example. |
@dave08 The The best you can do in a |
Is this going to make it into the release with 1.3? Even with experimental annotations (are those transitive, it relies on channels...) that would be preferable. |
We are focused on "for 1.0 release" issues in order to make release 1.0 soon and not to break non-experimental things in the future. |
Thank you very much for your work. In the proposed design, I tend to consider error-prone the need to use class MyActor : Actor() {
// I don't find very clear that doSomething may resume *before* the work being actually done. Since the function suspend, it would be natural (IMHO) to assume it resumes when the job is actually done.
// It is also very important to not forget to use `act`. But it is easy to forget, compiler won't help and missing usages of `act` may be hard to spot.
suspend fun doSomething() = act {
// work...
}
} Personally I'd prefer something like this: interface Actor<in T> {
// Here it is explicit, that the method suspend only until the message has been accepted.
suspend fun accept(message: T)
}
class MyActor : AbstractActor<MessageType>() {
// Yes it is more verbose. But there's nothing special we can forget without getting compilation error.
override suspend fun onMessage(message: MessageType) = when(message) {
is DoSomething -> // work...
}
} |
@jcornaz
Your concern is understandable, I will update guide and documentation accordingly. |
This is indeed somehow what I'm looking for. Except I still need to use
That sounds great, thanks. |
No, |
The missing feature for this use case is the native support for aspect programming, I consider Moreover |
* ``` | ||
* class ExampleActor : TypedActor<String>() { | ||
* | ||
* override suspend fun receive(string: String) = act { |
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.
Except I still need to use act right?
No, TypedActor doesn't have that method
Then I guess this example is wrong and act
is not needed here.
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.
yup, documentation leftover
69dc390
to
eaf9b7c
Compare
bc68d63
to
3179683
Compare
Leaving this one for a reference, I will return to the actors as soon as |
1837412
to
1748ce1
Compare
4a49830
to
aff8202
Compare
Fixes #87 and #169