tomthestorm
Why GenServer.call is synchronous?
Hi, I try understand GenServer .
Why GenServer.call is synchronous ? I want to get, in asynchronous way, information about state of the pid, but I can’t.
For example if I send 1000 of GenServer.call(pid,:get) via :
[ Task.await(Task.async(fn → Genserver.call(pid,:get) end)) , [ Task.await(Task.async(fn → Genserver.call(pid,:get) end)) , … , … ]
when I get the last one I have to wait 999 times.
Maybe it is stupid question , but for me GenServer.call should be asynchronous because we want read state and GenServer.cast should be synchronous cause we modify the state (and we should lock state during modification) .
But the situation is opposite and I don’t understand why .
Thanks in advance for answer .
Most Liked
benwilson512
There are two minor misconceptions here: call vs cast is not about read vs write. You can (and usually should) modify via call, and you often use call to read too. Call vs cast is literally about whether you as the client want to communicate async vs sync.
This becomes clearer if you think about the mechanism of communication: message passing. All message passing is async, so if you send a message to the genserver to read OR write and you want to know the result, the only way you can do so is to wait around for a reply message from the genserver. This is exactly what call does. If you want to write and you don’t care about the result, or if you want to tell the genserver to send you a reply and you’ll look at it later, you can consider cast.
The second misconception is that the genserver is some state that can be read directly by the client. As noted, all communication between processes is via message passing, and so if clients want to read a genserver, the genserver needs to send them a message. Genservers, like all processes, are single threaded, and so the message sending happens one after the next.
sztosz
Cast and call are not about whether we read or modify the state, but do we wan’t to wait for the response, and do nothing or are we not going to wait for the response (we don’t care about the response at all).
Cast and call both can modify state.
Call is synchronous by nature, because it hast to send a response for which calling process await.
And beside that messages in GenServer are read sequentially IIRC, so any computation done based on message are done sequentially too.
peerreynders
For some background this topic may be of some interest:
This may already be an indication that you are solving your problem in a less than optimal way in a process-oriented environment (there simply isn’t enough detail to know whether this is in fact true).
More often than not process state exists to enable the process to enact some sort of protocol in concert with other processes rather than the process simply acting as a container-of-state to be queried.
To a certain degree this has been a problem in some object-oriented designs and can be even more a problem with process-oriented solutions.
Some of my posts around “Tell, don’t Ask”:
- Why/When Not To Use GraphQL - #13 by peerreynders
- How to determine contexts with Phoenix 1.3 - #75 by peerreynders
- Agent vs Registry - #2 by peerreynders
You wouldn’t write code like that anyway. At the very least you would organize it more like:
funs = [fn -> Genserver.call(pid,:get) end, fn -> Genserver.call(pid,:get) end, … , … ]
tasks = Enum.map(funs, &Task.async/1)
results = Enum.map(tasks, &Task.await/2)
or
funs = [fn -> Genserver.call(pid,:get) end, fn -> Genserver.call(pid,:get) end, … , … ]
results =
funs
|> Enum.map(&Task.async/1)
|> Enum.map(&Task.await/2)
The idea being that you first launch all the tasks and only then you start waiting on them (in this particular case it wouldn’t make much of a difference as you are waiting on the same process anyway).
And ideally you would be using Task.Supervisor.async_stream/6 anyway, as that gives you better control of the level of concurrency that may be reasonable under the circumstances.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #hex









