Ask Pattern
The ask pattern provides request–response semantics between actors. An actor sends a message and waits for a reply, typically by using a future or awaiting a Task
.
Basic Flow
sequenceDiagram
participant Caller
participant Target
Caller->>Target: Request
Target-->>Caller: Response
In Proto.Actor you can use Context.Request
when the sender expects the recipient to know who sent the message. For an awaitable response, Context.RequestAsync<T>
or PID.RequestAsync<T>
returns a Task<T>
that completes when the reply arrives.
// ask a target actor for a reply
var response = await pid.RequestAsync<MyReply>(new MyRequest());
When to Use
- Querying another actor for state or service results.
- Bridging between actor code and external await-based APIs.
- Providing back-pressure by limiting concurrent pending requests.
Tell, Don’t Ask
In an asynchronous system it’s often better to emit events about your state rather than having others ask for it. By telling peers about changes, actors stay loosely coupled and can react when they are ready.
graph LR
producer((Producer))
consumer((Consumer))
evt(StateChanged)
class evt message
producer -- emits --> evt
evt --> consumer
public class Counter : IActor
{
private int _value;
public Task ReceiveAsync(IContext context)
{
switch (context.Message)
{
case Increment _:
_value++;
// tell others instead of expecting them to ask
EventStream.Instance.Publish(new CountChanged(_value));
break;
}
return Task.CompletedTask;
}
}
Listeners subscribe to the emitted events and can maintain local state without performing additional request–response roundtrips.
Reentrancy and Ask
Waiting on RequestAsync
inside an actor’s receive method suspends message processing until the task completes. Combine the ask pattern with
Reentrancy (RequestReenter
or ReenterAfter
) to keep the actor responsive. Without reentrancy, the actor cannot handle other messages and may cause a deadlock.
Deadlock Example
graph LR
A((Actor A))
B((Actor B))
req1(Request)
req2(Request)
class req1 message
class req2 message
A --> req1 --> B
B --> req2 --> A
If Actor A awaits a reply from Actor B while B simultaneously awaits a reply from A, neither actor can proceed. Reentrancy or redesigning the communication flow breaks the cycle.
See Actor Communication for an overview of messaging APIs and PID for additional request options.