Table of Contents

Lifecycle & dispose

Own the VoipClient for the life of your application (or per tenant) and dispose it exactly once on shutdown.

Construction

using var client = new VoipClient(new VoipConfiguration { /* … */ });

using (or an explicit Dispose()) guarantees teardown. The client is not meant to be constructed per call — construct once and reuse.

What Dispose() does

  • Unregisters every registered line (reusing each registration's Call-ID and next CSeq per RFC 3261 §10.2.2, so the registrar actually clears the binding).
  • Makes a best-effort BYE for still-active calls. A BYE that faults during teardown is observed and logged rather than dropped as an unobserved task exception.
  • Releases audio devices and media sessions.

Graceful shutdown

For a clean shutdown, prefer to end work explicitly before disposing:

foreach (var call in activeCalls)
    await call.HangupAsync();      // BYE, awaited

await line.UnregisterAsync();      // awaited unregister

client.Dispose();                  // best-effort cleanup for anything remaining

Explicit HangupAsync/UnregisterAsync are awaitable and deterministic; the best-effort cleanup in Dispose() is a safety net, not a substitute for orderly teardown — on a process exit racing the network, an awaited unregister is more reliable than relying on dispose alone.

Hosting

In a hosted/DI application, tie the client's lifetime to the host and dispose it in the shutdown path (e.g. a hosted service's StopAsync, or the DI container disposing a singleton). Do not let the process exit abruptly while calls or registrations are live if you need clean teardown on the far end.