Why GenServer.call is synchronous?

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”:

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.

2 Likes