Keep in mind, ask() is pretty optimized -- it's creating pseudo-Actors under the hood, but as I understand it they're a bit lighter-weight than creating ordinary Actors. So I wouldn't necessarily count on your own Actor being more efficient. The per-request Actor may be the right way to go architecturally -- that actually wouldn't surprise me -- but it's not obvious which way would be fastest.
As for the concurrency issues involved with using ask inside an Actor, you *may* want to take a look at
the Requester library, which adds request(), a higher-level variant of ask() that is specifically designed to work well for complex scenarios like you are describing. Assuming the requests to the three other Actors are all independent, this comes out something like (in Scala):
def receive = {
case Thingy(x) => {
// Send the requests:
val reqA = OtherActor1.request(Foo(x))
val reqB = OtherActor2.request(Bar(x))
val reqC = OtherActor3.request(Baz(x))
// Then collect the responses:
for {
a <- reqA
b <- reqB
c <- reqC
}
// We have all the responses, so send the collated response back to the original sender:
sender ! MyResponse(a, b, c)
}
}
All the concurrency problems are handled under the hood, by quietly looping the responses back into the receive function.
It *does* add a small amount of extra overhead, and I know you're sensitive to that (and perhaps more importantly, I don't think Requester has been tried from Java yet, so there may be API issues), but it might at least provide an illustration of how to deal with complex multi-Actor protocols like this. (I deal with this sort of thing a *lot* in Querki, which is why I developed the library.)