Skip to main content

Advanced: Client Results & Cancellation

Client results

The server can invoke a method on a single client and wait for its return value. The client must have registered a handler for the method; the handler's non-None return value is sent back as the result.

class EchoHub(Hub):
async def ping_client(self) -> str:
return await self.clients.caller.invoke("client_method", "hello-from-server")

Client side:

async def client_method(text: str) -> str:
return f"ack:{text}"

connection.on("client_method", client_method)

result = await connection.invoke("ping_client")
print(result) # "ack:hello-from-server"
  • invoke(method, *args) returns the client result, or raises HubException if the client returns an error.
  • An optional timeout raises SignalRError if the client does not answer in time.
  • Client results are tracked per connection with server-generated invocation ids (s1, s2, …).

How it works

  1. The server registers a future for a fresh invocation id.
  2. It sends an Invocation message to the client with that invocation id.
  3. The client runs its handlers and replies with a Completion carrying the return value (or an error).
  4. The server resolves the future.

Cancellation

Clients can cancel an in-flight invocation with CancelInvocation. The server responds in two ways:

  • Sets the invocation's cancellation_token (an asyncio.Event).
  • Cancels the underlying task running the hub method.
class ProbeHub(Hub):
async def cancelable(self, n: int, cancellation_token: Any = None) -> int:
try:
for i in range(n):
await asyncio.sleep(0.05)
if cancellation_token is not None and cancellation_token.is_set():
return i
return n
except asyncio.CancelledError:
return -1

The aiosignalr client sends a CancelInvocation automatically when a stream() iterator is abandoned early.

Parallel invocations

By default each connection processes one invocation at a time (maximum_parallel_invocations = 1), mirroring the reference server. Raise it to allow concurrent hub methods on a single connection:

server = SignalRServer(MyHub, options=ServerOptions(maximum_parallel_invocations=4))

Streaming methods always start on their own task so they do not block the invocation semaphore while streaming.